eslint-plugin-rxjs-x 0.7.3 → 0.7.5

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 CHANGED
@@ -120,6 +120,6 @@ The package includes the following rules.
120
120
  | [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | ✅ 🔒 | 🔧 | 💡 | 💭 | |
121
121
  | [prefer-root-operators](docs/rules/prefer-root-operators.md) | Disallow importing operators from `rxjs/operators`. | ✅ 🔒 | 🔧 | 💡 | | |
122
122
  | [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | 💭 | |
123
- | [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | ✅ 🔒 | | | 💭 | |
123
+ | [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError` or `Subject.error`. | ✅ 🔒 | | | 💭 | |
124
124
 
125
125
  <!-- end auto-generated rules list -->
package/dist/index.d.mts CHANGED
@@ -150,7 +150,9 @@ declare const rxjsX: {
150
150
  'no-subscribe-in-pipe': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
151
151
  'no-tap': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
152
152
  'no-topromise': TSESLint.RuleModule<"forbidden" | "suggestLastValueFromWithDefault" | "suggestLastValueFrom" | "suggestFirstValueFrom", [], RxjsXRuleDocs, TSESLint.RuleListener>;
153
- 'no-unbound-methods': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
153
+ 'no-unbound-methods': TSESLint.RuleModule<"forbidden", readonly {
154
+ allowTypes?: string[];
155
+ }[], RxjsXRuleDocs, TSESLint.RuleListener>;
154
156
  'no-unsafe-catch': TSESLint.RuleModule<"forbidden", readonly {
155
157
  observable?: string;
156
158
  }[], RxjsXRuleDocs, TSESLint.RuleListener>;
package/dist/index.d.ts CHANGED
@@ -150,7 +150,9 @@ declare const rxjsX: {
150
150
  'no-subscribe-in-pipe': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
151
151
  'no-tap': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
152
152
  'no-topromise': TSESLint.RuleModule<"forbidden" | "suggestLastValueFromWithDefault" | "suggestLastValueFrom" | "suggestFirstValueFrom", [], RxjsXRuleDocs, TSESLint.RuleListener>;
153
- 'no-unbound-methods': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
153
+ 'no-unbound-methods': TSESLint.RuleModule<"forbidden", readonly {
154
+ allowTypes?: string[];
155
+ }[], RxjsXRuleDocs, TSESLint.RuleListener>;
154
156
  'no-unsafe-catch': TSESLint.RuleModule<"forbidden", readonly {
155
157
  observable?: string;
156
158
  }[], RxjsXRuleDocs, TSESLint.RuleListener>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// package.json
2
2
  var name = "eslint-plugin-rxjs-x";
3
- var version = "0.7.3";
3
+ var version = "0.7.5";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -284,6 +284,9 @@ function isPropertyDefinition(node) {
284
284
  function isUnaryExpression(node) {
285
285
  return node.type === _utils.AST_NODE_TYPES.UnaryExpression;
286
286
  }
287
+ function isUnionType(node) {
288
+ return node.type === _utils.AST_NODE_TYPES.TSUnionType;
289
+ }
287
290
 
288
291
  // src/etc/get-type-services.ts
289
292
  function getTypeServices(context) {
@@ -389,6 +392,44 @@ function findIsLastOperatorOrderValid(pipeCallExpression, operatorsRegExp, allow
389
392
  return { isOrderValid, operatorNode };
390
393
  }
391
394
 
395
+ // src/constants.ts
396
+ var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
397
+ var SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS = [
398
+ "combineLatest",
399
+ "forkJoin"
400
+ ];
401
+ var DEFAULT_UNBOUND_ALLOWED_TYPES = [
402
+ "Signal"
403
+ ];
404
+ var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
405
+ "count",
406
+ "defaultIfEmpty",
407
+ "endWith",
408
+ "every",
409
+ "finalize",
410
+ "finally",
411
+ "isEmpty",
412
+ "last",
413
+ "max",
414
+ "min",
415
+ "publish",
416
+ "publishBehavior",
417
+ "publishLast",
418
+ "publishReplay",
419
+ "reduce",
420
+ "share",
421
+ "shareReplay",
422
+ "skipLast",
423
+ "takeLast",
424
+ "throwIfEmpty",
425
+ "toArray"
426
+ ];
427
+
428
+ // src/utils/is-sources-object-accepting-static-observable-creator.ts
429
+ function isSourcesObjectAcceptingStaticObservableCreator(expression) {
430
+ return isCallExpression(expression) && isIdentifier(expression.callee) && SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS.includes(expression.callee.name);
431
+ }
432
+
392
433
  // src/utils/rule-creator.ts
393
434
 
394
435
  var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
@@ -717,7 +758,7 @@ var finnishRule = ruleCreator({
717
758
  "ObjectExpression > Property[computed=false] > Identifier": (node) => {
718
759
  if (validate.properties) {
719
760
  const parent = node.parent;
720
- if (node === parent.key) {
761
+ if (node === parent.key && !isSourcesObjectAcceptingStaticObservableCreator(parent.parent.parent)) {
721
762
  checkNode(node);
722
763
  }
723
764
  }
@@ -894,7 +935,7 @@ var noAsyncSubscribeRule = ruleCreator({
894
935
  requiresTypeChecking: true
895
936
  },
896
937
  messages: {
897
- forbidden: "Passing async functions to subscribe is forbidden."
938
+ forbidden: "Passing async functions to subscribe is forbidden. Use operators like `concatMap` or `switchMap` to avoid race conditions."
898
939
  },
899
940
  schema: [],
900
941
  type: "problem"
@@ -902,27 +943,39 @@ var noAsyncSubscribeRule = ruleCreator({
902
943
  name: "no-async-subscribe",
903
944
  create: (context) => {
904
945
  const { couldBeObservable } = getTypeServices(context);
905
- function checkNode(node) {
906
- const parentNode = node.parent;
946
+ function report(node) {
947
+ const { loc } = node;
948
+ const asyncLoc = {
949
+ ...loc,
950
+ end: {
951
+ ...loc.start,
952
+ column: loc.start.column + 5
953
+ }
954
+ };
955
+ context.report({
956
+ messageId: "forbidden",
957
+ loc: asyncLoc
958
+ });
959
+ }
960
+ function checkSingleNext(funcNode) {
961
+ const parentNode = funcNode.parent;
907
962
  const callee = parentNode.callee;
908
963
  if (couldBeObservable(callee.object)) {
909
- const { loc } = node;
910
- const asyncLoc = {
911
- ...loc,
912
- end: {
913
- ...loc.start,
914
- column: loc.start.column + 5
915
- }
916
- };
917
- context.report({
918
- messageId: "forbidden",
919
- loc: asyncLoc
920
- });
964
+ report(funcNode);
965
+ }
966
+ }
967
+ function checkObserverObjectProp(propNode) {
968
+ const parentNode = propNode.parent.parent;
969
+ const callee = parentNode.callee;
970
+ if (couldBeObservable(callee.object) && (isArrowFunctionExpression(propNode.value) || isFunctionExpression(propNode.value))) {
971
+ report(propNode.value);
921
972
  }
922
973
  }
923
974
  return {
924
- "CallExpression[callee.property.name='subscribe'] > FunctionExpression[async=true]": checkNode,
925
- "CallExpression[callee.property.name='subscribe'] > ArrowFunctionExpression[async=true]": checkNode
975
+ "CallExpression[callee.property.name='subscribe'] > FunctionExpression[async=true]": checkSingleNext,
976
+ "CallExpression[callee.property.name='subscribe'] > ArrowFunctionExpression[async=true]": checkSingleNext,
977
+ "CallExpression[callee.property.name='subscribe'] > ObjectExpression > Property[key.name='next'][value.type=\"ArrowFunctionExpression\"][value.async=true]": checkObserverObjectProp,
978
+ "CallExpression[callee.property.name='subscribe'] > ObjectExpression > Property[key.name='next'][value.type=\"FunctionExpression\"][value.async=true]": checkObserverObjectProp
926
979
  };
927
980
  }
928
981
  });
@@ -1028,34 +1081,6 @@ var noCreateRule = ruleCreator({
1028
1081
 
1029
1082
 
1030
1083
 
1031
-
1032
- // src/constants.ts
1033
- var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
1034
- var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
1035
- "count",
1036
- "defaultIfEmpty",
1037
- "endWith",
1038
- "every",
1039
- "finalize",
1040
- "finally",
1041
- "isEmpty",
1042
- "last",
1043
- "max",
1044
- "min",
1045
- "publish",
1046
- "publishBehavior",
1047
- "publishLast",
1048
- "publishReplay",
1049
- "reduce",
1050
- "share",
1051
- "shareReplay",
1052
- "skipLast",
1053
- "takeLast",
1054
- "throwIfEmpty",
1055
- "toArray"
1056
- ];
1057
-
1058
- // src/rules/no-cyclic-action.ts
1059
1084
  function isTypeReference2(type) {
1060
1085
  return Boolean(type.target);
1061
1086
  }
@@ -1180,21 +1205,25 @@ var noExplicitGenericsRule = ruleCreator({
1180
1205
  });
1181
1206
  }
1182
1207
  function checkBehaviorSubjects(node) {
1208
+ var _a;
1183
1209
  const parent = node.parent;
1184
1210
  const {
1185
1211
  arguments: [value]
1186
1212
  } = parent;
1187
- if (isArrayExpression(value) || isObjectExpression(value)) {
1213
+ const typeArgs = (_a = parent.typeArguments) == null ? void 0 : _a.params[0];
1214
+ if (isArrayExpression(value) || isObjectExpression(value) || typeArgs && isUnionType(typeArgs)) {
1188
1215
  return;
1189
1216
  }
1190
1217
  report(node);
1191
1218
  }
1192
1219
  function checkNotifications(node) {
1220
+ var _a;
1193
1221
  const parent = node.parent;
1194
1222
  const {
1195
1223
  arguments: [, value]
1196
1224
  } = parent;
1197
- if (isArrayExpression(value) || isObjectExpression(value)) {
1225
+ const typeArgs = (_a = parent.typeArguments) == null ? void 0 : _a.params[0];
1226
+ if (isArrayExpression(value) || isObjectExpression(value) || typeArgs && isUnionType(typeArgs)) {
1198
1227
  return;
1199
1228
  }
1200
1229
  report(node);
@@ -3102,8 +3131,9 @@ import { ${functionName} } from ${quote}rxjs${quote};`
3102
3131
 
3103
3132
  // src/rules/no-unbound-methods.ts
3104
3133
 
3134
+ var defaultOptions11 = [];
3105
3135
  var noUnboundMethodsRule = ruleCreator({
3106
- defaultOptions: [],
3136
+ defaultOptions: defaultOptions11,
3107
3137
  meta: {
3108
3138
  docs: {
3109
3139
  description: "Disallow passing unbound methods.",
@@ -3113,7 +3143,14 @@ var noUnboundMethodsRule = ruleCreator({
3113
3143
  messages: {
3114
3144
  forbidden: "Unbound methods are forbidden."
3115
3145
  },
3116
- schema: [],
3146
+ schema: [
3147
+ {
3148
+ properties: {
3149
+ allowTypes: { type: "array", items: { type: "string" }, description: "An array of function types that are allowed to be passed unbound.", default: DEFAULT_UNBOUND_ALLOWED_TYPES }
3150
+ },
3151
+ type: "object"
3152
+ }
3153
+ ],
3117
3154
  type: "problem"
3118
3155
  },
3119
3156
  name: "no-unbound-methods",
@@ -3121,9 +3158,14 @@ var noUnboundMethodsRule = ruleCreator({
3121
3158
  const { getTypeAtLocation } = _utils.ESLintUtils.getParserServices(context);
3122
3159
  const { couldBeObservable, couldBeSubscription } = getTypeServices(context);
3123
3160
  const nodeMap = /* @__PURE__ */ new WeakMap();
3161
+ const [config = {}] = context.options;
3162
+ const { allowTypes = [] } = config;
3124
3163
  function mapArguments(node) {
3125
3164
  node.arguments.filter(isMemberExpression).forEach((arg) => {
3126
3165
  const argType = getTypeAtLocation(arg);
3166
+ if (allowTypes.some((t) => couldBeType(argType, t))) {
3167
+ return;
3168
+ }
3127
3169
  if (argType.getCallSignatures().length > 0) {
3128
3170
  nodeMap.set(arg);
3129
3171
  }
@@ -3166,9 +3208,9 @@ var noUnboundMethodsRule = ruleCreator({
3166
3208
 
3167
3209
  // src/rules/no-unsafe-catch.ts
3168
3210
 
3169
- var defaultOptions11 = [];
3211
+ var defaultOptions12 = [];
3170
3212
  var noUnsafeCatchRule = ruleCreator({
3171
- defaultOptions: defaultOptions11,
3213
+ defaultOptions: defaultOptions12,
3172
3214
  meta: {
3173
3215
  docs: {
3174
3216
  description: "Disallow unsafe `catchError` usage in effects and epics.",
@@ -3227,9 +3269,9 @@ var noUnsafeCatchRule = ruleCreator({
3227
3269
 
3228
3270
  // src/rules/no-unsafe-first.ts
3229
3271
 
3230
- var defaultOptions12 = [];
3272
+ var defaultOptions13 = [];
3231
3273
  var noUnsafeFirstRule = ruleCreator({
3232
- defaultOptions: defaultOptions12,
3274
+ defaultOptions: defaultOptions13,
3233
3275
  meta: {
3234
3276
  docs: {
3235
3277
  description: "Disallow unsafe `first`/`take` usage in effects and epics.",
@@ -3351,9 +3393,9 @@ var noUnsafeSubjectNext = ruleCreator({
3351
3393
  // src/rules/no-unsafe-switchmap.ts
3352
3394
 
3353
3395
  var _decamelize = require('decamelize'); var _decamelize2 = _interopRequireDefault(_decamelize);
3354
- var defaultOptions13 = [];
3396
+ var defaultOptions14 = [];
3355
3397
  var noUnsafeSwitchmapRule = ruleCreator({
3356
- defaultOptions: defaultOptions13,
3398
+ defaultOptions: defaultOptions14,
3357
3399
  meta: {
3358
3400
  docs: {
3359
3401
  description: "Disallow unsafe `switchMap` usage in effects and epics.",
@@ -3476,9 +3518,9 @@ var noUnsafeSwitchmapRule = ruleCreator({
3476
3518
 
3477
3519
  // src/rules/no-unsafe-takeuntil.ts
3478
3520
 
3479
- var defaultOptions14 = [];
3521
+ var defaultOptions15 = [];
3480
3522
  var noUnsafeTakeuntilRule = ruleCreator({
3481
- defaultOptions: defaultOptions14,
3523
+ defaultOptions: defaultOptions15,
3482
3524
  meta: {
3483
3525
  docs: {
3484
3526
  description: "Disallow applying operators after `takeUntil`.",
@@ -3539,9 +3581,9 @@ var noUnsafeTakeuntilRule = ruleCreator({
3539
3581
  });
3540
3582
 
3541
3583
  // src/rules/prefer-observer.ts
3542
- var defaultOptions15 = [];
3584
+ var defaultOptions16 = [];
3543
3585
  var preferObserverRule = ruleCreator({
3544
- defaultOptions: defaultOptions15,
3586
+ defaultOptions: defaultOptions16,
3545
3587
  meta: {
3546
3588
  docs: {
3547
3589
  description: "Disallow passing separate handlers to `subscribe` and `tap`.",
@@ -3777,9 +3819,9 @@ var preferRootOperatorsRule = ruleCreator({
3777
3819
 
3778
3820
  // src/rules/suffix-subjects.ts
3779
3821
 
3780
- var defaultOptions16 = [];
3822
+ var defaultOptions17 = [];
3781
3823
  var suffixSubjectsRule = ruleCreator({
3782
- defaultOptions: defaultOptions16,
3824
+ defaultOptions: defaultOptions17,
3783
3825
  meta: {
3784
3826
  docs: {
3785
3827
  description: "Enforce the use of a suffix in subject identifiers.",
@@ -3911,7 +3953,7 @@ var suffixSubjectsRule = ruleCreator({
3911
3953
  "ObjectExpression > Property[computed=false] > Identifier": (node) => {
3912
3954
  if (validate.properties) {
3913
3955
  const parent = node.parent;
3914
- if (node === parent.key) {
3956
+ if (node === parent.key && !isSourcesObjectAcceptingStaticObservableCreator(parent.parent.parent)) {
3915
3957
  checkNode(node);
3916
3958
  }
3917
3959
  }
@@ -3976,12 +4018,12 @@ var suffixSubjectsRule = ruleCreator({
3976
4018
  // src/rules/throw-error.ts
3977
4019
 
3978
4020
 
3979
- var defaultOptions17 = [];
4021
+ var defaultOptions18 = [];
3980
4022
  var throwErrorRule = ruleCreator({
3981
- defaultOptions: defaultOptions17,
4023
+ defaultOptions: defaultOptions18,
3982
4024
  meta: {
3983
4025
  docs: {
3984
- description: "Enforce passing only `Error` values to `throwError`.",
4026
+ description: "Enforce passing only `Error` values to `throwError` or `Subject.error`.",
3985
4027
  recommended: {
3986
4028
  recommended: true,
3987
4029
  strict: [{ allowThrowingAny: false, allowThrowingUnknown: false }]
@@ -4005,14 +4047,14 @@ var throwErrorRule = ruleCreator({
4005
4047
  name: "throw-error",
4006
4048
  create: (context) => {
4007
4049
  const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = _utils.ESLintUtils.getParserServices(context);
4008
- const { couldBeObservable } = getTypeServices(context);
4050
+ const { couldBeObservable, couldBeSubject } = getTypeServices(context);
4009
4051
  const [config = {}] = context.options;
4010
4052
  const { allowThrowingAny = true, allowThrowingUnknown = true } = config;
4011
- function checkThrowArgument(node) {
4053
+ function checkThrowArgument(node, noFunction = false) {
4012
4054
  var _a;
4013
4055
  let type = getTypeAtLocation(node);
4014
4056
  let reportNode = node;
4015
- if (couldBeFunction(type)) {
4057
+ if (!noFunction && couldBeFunction(type)) {
4016
4058
  reportNode = (_a = node.body) != null ? _a : node;
4017
4059
  const tsNode = esTreeNodeToTSNodeMap.get(node);
4018
4060
  const annotation = tsNode.type;
@@ -4033,7 +4075,7 @@ var throwErrorRule = ruleCreator({
4033
4075
  node: reportNode
4034
4076
  });
4035
4077
  }
4036
- function checkNode(node) {
4078
+ function checkThrowError(node) {
4037
4079
  if (couldBeObservable(node)) {
4038
4080
  const [arg] = node.arguments;
4039
4081
  if (arg) {
@@ -4041,13 +4083,18 @@ var throwErrorRule = ruleCreator({
4041
4083
  }
4042
4084
  }
4043
4085
  }
4044
- return {
4045
- "CallExpression[callee.name='throwError']": (node) => {
4046
- checkNode(node);
4047
- },
4048
- "CallExpression[callee.property.name='throwError']": (node) => {
4049
- checkNode(node);
4086
+ function checkSubjectError(node) {
4087
+ if (isMemberExpression(node.callee) && couldBeSubject(node.callee.object)) {
4088
+ const [arg] = node.arguments;
4089
+ if (arg) {
4090
+ checkThrowArgument(arg, true);
4091
+ }
4050
4092
  }
4093
+ }
4094
+ return {
4095
+ "CallExpression[callee.name='throwError']": checkThrowError,
4096
+ "CallExpression[callee.property.name='throwError']": checkThrowError,
4097
+ "CallExpression[callee.property.name='error']": checkSubjectError
4051
4098
  };
4052
4099
  }
4053
4100
  });
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var name = "eslint-plugin-rxjs-x";
3
- var version = "0.7.3";
3
+ var version = "0.7.5";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -284,6 +284,9 @@ function isPropertyDefinition(node) {
284
284
  function isUnaryExpression(node) {
285
285
  return node.type === AST_NODE_TYPES.UnaryExpression;
286
286
  }
287
+ function isUnionType(node) {
288
+ return node.type === AST_NODE_TYPES.TSUnionType;
289
+ }
287
290
 
288
291
  // src/etc/get-type-services.ts
289
292
  function getTypeServices(context) {
@@ -389,6 +392,44 @@ function findIsLastOperatorOrderValid(pipeCallExpression, operatorsRegExp, allow
389
392
  return { isOrderValid, operatorNode };
390
393
  }
391
394
 
395
+ // src/constants.ts
396
+ var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
397
+ var SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS = [
398
+ "combineLatest",
399
+ "forkJoin"
400
+ ];
401
+ var DEFAULT_UNBOUND_ALLOWED_TYPES = [
402
+ "Signal"
403
+ ];
404
+ var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
405
+ "count",
406
+ "defaultIfEmpty",
407
+ "endWith",
408
+ "every",
409
+ "finalize",
410
+ "finally",
411
+ "isEmpty",
412
+ "last",
413
+ "max",
414
+ "min",
415
+ "publish",
416
+ "publishBehavior",
417
+ "publishLast",
418
+ "publishReplay",
419
+ "reduce",
420
+ "share",
421
+ "shareReplay",
422
+ "skipLast",
423
+ "takeLast",
424
+ "throwIfEmpty",
425
+ "toArray"
426
+ ];
427
+
428
+ // src/utils/is-sources-object-accepting-static-observable-creator.ts
429
+ function isSourcesObjectAcceptingStaticObservableCreator(expression) {
430
+ return isCallExpression(expression) && isIdentifier(expression.callee) && SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS.includes(expression.callee.name);
431
+ }
432
+
392
433
  // src/utils/rule-creator.ts
393
434
  import { ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
394
435
  var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
@@ -717,7 +758,7 @@ var finnishRule = ruleCreator({
717
758
  "ObjectExpression > Property[computed=false] > Identifier": (node) => {
718
759
  if (validate.properties) {
719
760
  const parent = node.parent;
720
- if (node === parent.key) {
761
+ if (node === parent.key && !isSourcesObjectAcceptingStaticObservableCreator(parent.parent.parent)) {
721
762
  checkNode(node);
722
763
  }
723
764
  }
@@ -894,7 +935,7 @@ var noAsyncSubscribeRule = ruleCreator({
894
935
  requiresTypeChecking: true
895
936
  },
896
937
  messages: {
897
- forbidden: "Passing async functions to subscribe is forbidden."
938
+ forbidden: "Passing async functions to subscribe is forbidden. Use operators like `concatMap` or `switchMap` to avoid race conditions."
898
939
  },
899
940
  schema: [],
900
941
  type: "problem"
@@ -902,27 +943,39 @@ var noAsyncSubscribeRule = ruleCreator({
902
943
  name: "no-async-subscribe",
903
944
  create: (context) => {
904
945
  const { couldBeObservable } = getTypeServices(context);
905
- function checkNode(node) {
906
- const parentNode = node.parent;
946
+ function report(node) {
947
+ const { loc } = node;
948
+ const asyncLoc = {
949
+ ...loc,
950
+ end: {
951
+ ...loc.start,
952
+ column: loc.start.column + 5
953
+ }
954
+ };
955
+ context.report({
956
+ messageId: "forbidden",
957
+ loc: asyncLoc
958
+ });
959
+ }
960
+ function checkSingleNext(funcNode) {
961
+ const parentNode = funcNode.parent;
907
962
  const callee = parentNode.callee;
908
963
  if (couldBeObservable(callee.object)) {
909
- const { loc } = node;
910
- const asyncLoc = {
911
- ...loc,
912
- end: {
913
- ...loc.start,
914
- column: loc.start.column + 5
915
- }
916
- };
917
- context.report({
918
- messageId: "forbidden",
919
- loc: asyncLoc
920
- });
964
+ report(funcNode);
965
+ }
966
+ }
967
+ function checkObserverObjectProp(propNode) {
968
+ const parentNode = propNode.parent.parent;
969
+ const callee = parentNode.callee;
970
+ if (couldBeObservable(callee.object) && (isArrowFunctionExpression(propNode.value) || isFunctionExpression(propNode.value))) {
971
+ report(propNode.value);
921
972
  }
922
973
  }
923
974
  return {
924
- "CallExpression[callee.property.name='subscribe'] > FunctionExpression[async=true]": checkNode,
925
- "CallExpression[callee.property.name='subscribe'] > ArrowFunctionExpression[async=true]": checkNode
975
+ "CallExpression[callee.property.name='subscribe'] > FunctionExpression[async=true]": checkSingleNext,
976
+ "CallExpression[callee.property.name='subscribe'] > ArrowFunctionExpression[async=true]": checkSingleNext,
977
+ "CallExpression[callee.property.name='subscribe'] > ObjectExpression > Property[key.name='next'][value.type=\"ArrowFunctionExpression\"][value.async=true]": checkObserverObjectProp,
978
+ "CallExpression[callee.property.name='subscribe'] > ObjectExpression > Property[key.name='next'][value.type=\"FunctionExpression\"][value.async=true]": checkObserverObjectProp
926
979
  };
927
980
  }
928
981
  });
@@ -1028,34 +1081,6 @@ var noCreateRule = ruleCreator({
1028
1081
  import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
1029
1082
  import { stripIndent as stripIndent3 } from "common-tags";
1030
1083
  import ts5 from "typescript";
1031
-
1032
- // src/constants.ts
1033
- var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
1034
- var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
1035
- "count",
1036
- "defaultIfEmpty",
1037
- "endWith",
1038
- "every",
1039
- "finalize",
1040
- "finally",
1041
- "isEmpty",
1042
- "last",
1043
- "max",
1044
- "min",
1045
- "publish",
1046
- "publishBehavior",
1047
- "publishLast",
1048
- "publishReplay",
1049
- "reduce",
1050
- "share",
1051
- "shareReplay",
1052
- "skipLast",
1053
- "takeLast",
1054
- "throwIfEmpty",
1055
- "toArray"
1056
- ];
1057
-
1058
- // src/rules/no-cyclic-action.ts
1059
1084
  function isTypeReference2(type) {
1060
1085
  return Boolean(type.target);
1061
1086
  }
@@ -1180,21 +1205,25 @@ var noExplicitGenericsRule = ruleCreator({
1180
1205
  });
1181
1206
  }
1182
1207
  function checkBehaviorSubjects(node) {
1208
+ var _a;
1183
1209
  const parent = node.parent;
1184
1210
  const {
1185
1211
  arguments: [value]
1186
1212
  } = parent;
1187
- if (isArrayExpression(value) || isObjectExpression(value)) {
1213
+ const typeArgs = (_a = parent.typeArguments) == null ? void 0 : _a.params[0];
1214
+ if (isArrayExpression(value) || isObjectExpression(value) || typeArgs && isUnionType(typeArgs)) {
1188
1215
  return;
1189
1216
  }
1190
1217
  report(node);
1191
1218
  }
1192
1219
  function checkNotifications(node) {
1220
+ var _a;
1193
1221
  const parent = node.parent;
1194
1222
  const {
1195
1223
  arguments: [, value]
1196
1224
  } = parent;
1197
- if (isArrayExpression(value) || isObjectExpression(value)) {
1225
+ const typeArgs = (_a = parent.typeArguments) == null ? void 0 : _a.params[0];
1226
+ if (isArrayExpression(value) || isObjectExpression(value) || typeArgs && isUnionType(typeArgs)) {
1198
1227
  return;
1199
1228
  }
1200
1229
  report(node);
@@ -3102,8 +3131,9 @@ import { ${functionName} } from ${quote}rxjs${quote};`
3102
3131
 
3103
3132
  // src/rules/no-unbound-methods.ts
3104
3133
  import { ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
3134
+ var defaultOptions11 = [];
3105
3135
  var noUnboundMethodsRule = ruleCreator({
3106
- defaultOptions: [],
3136
+ defaultOptions: defaultOptions11,
3107
3137
  meta: {
3108
3138
  docs: {
3109
3139
  description: "Disallow passing unbound methods.",
@@ -3113,7 +3143,14 @@ var noUnboundMethodsRule = ruleCreator({
3113
3143
  messages: {
3114
3144
  forbidden: "Unbound methods are forbidden."
3115
3145
  },
3116
- schema: [],
3146
+ schema: [
3147
+ {
3148
+ properties: {
3149
+ allowTypes: { type: "array", items: { type: "string" }, description: "An array of function types that are allowed to be passed unbound.", default: DEFAULT_UNBOUND_ALLOWED_TYPES }
3150
+ },
3151
+ type: "object"
3152
+ }
3153
+ ],
3117
3154
  type: "problem"
3118
3155
  },
3119
3156
  name: "no-unbound-methods",
@@ -3121,9 +3158,14 @@ var noUnboundMethodsRule = ruleCreator({
3121
3158
  const { getTypeAtLocation } = ESLintUtils9.getParserServices(context);
3122
3159
  const { couldBeObservable, couldBeSubscription } = getTypeServices(context);
3123
3160
  const nodeMap = /* @__PURE__ */ new WeakMap();
3161
+ const [config = {}] = context.options;
3162
+ const { allowTypes = [] } = config;
3124
3163
  function mapArguments(node) {
3125
3164
  node.arguments.filter(isMemberExpression).forEach((arg) => {
3126
3165
  const argType = getTypeAtLocation(arg);
3166
+ if (allowTypes.some((t) => couldBeType(argType, t))) {
3167
+ return;
3168
+ }
3127
3169
  if (argType.getCallSignatures().length > 0) {
3128
3170
  nodeMap.set(arg);
3129
3171
  }
@@ -3166,9 +3208,9 @@ var noUnboundMethodsRule = ruleCreator({
3166
3208
 
3167
3209
  // src/rules/no-unsafe-catch.ts
3168
3210
  import { stripIndent as stripIndent5 } from "common-tags";
3169
- var defaultOptions11 = [];
3211
+ var defaultOptions12 = [];
3170
3212
  var noUnsafeCatchRule = ruleCreator({
3171
- defaultOptions: defaultOptions11,
3213
+ defaultOptions: defaultOptions12,
3172
3214
  meta: {
3173
3215
  docs: {
3174
3216
  description: "Disallow unsafe `catchError` usage in effects and epics.",
@@ -3227,9 +3269,9 @@ var noUnsafeCatchRule = ruleCreator({
3227
3269
 
3228
3270
  // src/rules/no-unsafe-first.ts
3229
3271
  import { stripIndent as stripIndent6 } from "common-tags";
3230
- var defaultOptions12 = [];
3272
+ var defaultOptions13 = [];
3231
3273
  var noUnsafeFirstRule = ruleCreator({
3232
- defaultOptions: defaultOptions12,
3274
+ defaultOptions: defaultOptions13,
3233
3275
  meta: {
3234
3276
  docs: {
3235
3277
  description: "Disallow unsafe `first`/`take` usage in effects and epics.",
@@ -3351,9 +3393,9 @@ var noUnsafeSubjectNext = ruleCreator({
3351
3393
  // src/rules/no-unsafe-switchmap.ts
3352
3394
  import { stripIndent as stripIndent7 } from "common-tags";
3353
3395
  import decamelize from "decamelize";
3354
- var defaultOptions13 = [];
3396
+ var defaultOptions14 = [];
3355
3397
  var noUnsafeSwitchmapRule = ruleCreator({
3356
- defaultOptions: defaultOptions13,
3398
+ defaultOptions: defaultOptions14,
3357
3399
  meta: {
3358
3400
  docs: {
3359
3401
  description: "Disallow unsafe `switchMap` usage in effects and epics.",
@@ -3476,9 +3518,9 @@ var noUnsafeSwitchmapRule = ruleCreator({
3476
3518
 
3477
3519
  // src/rules/no-unsafe-takeuntil.ts
3478
3520
  import { stripIndent as stripIndent8 } from "common-tags";
3479
- var defaultOptions14 = [];
3521
+ var defaultOptions15 = [];
3480
3522
  var noUnsafeTakeuntilRule = ruleCreator({
3481
- defaultOptions: defaultOptions14,
3523
+ defaultOptions: defaultOptions15,
3482
3524
  meta: {
3483
3525
  docs: {
3484
3526
  description: "Disallow applying operators after `takeUntil`.",
@@ -3539,9 +3581,9 @@ var noUnsafeTakeuntilRule = ruleCreator({
3539
3581
  });
3540
3582
 
3541
3583
  // src/rules/prefer-observer.ts
3542
- var defaultOptions15 = [];
3584
+ var defaultOptions16 = [];
3543
3585
  var preferObserverRule = ruleCreator({
3544
- defaultOptions: defaultOptions15,
3586
+ defaultOptions: defaultOptions16,
3545
3587
  meta: {
3546
3588
  docs: {
3547
3589
  description: "Disallow passing separate handlers to `subscribe` and `tap`.",
@@ -3777,9 +3819,9 @@ var preferRootOperatorsRule = ruleCreator({
3777
3819
 
3778
3820
  // src/rules/suffix-subjects.ts
3779
3821
  import { AST_NODE_TYPES as AST_NODE_TYPES9, ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
3780
- var defaultOptions16 = [];
3822
+ var defaultOptions17 = [];
3781
3823
  var suffixSubjectsRule = ruleCreator({
3782
- defaultOptions: defaultOptions16,
3824
+ defaultOptions: defaultOptions17,
3783
3825
  meta: {
3784
3826
  docs: {
3785
3827
  description: "Enforce the use of a suffix in subject identifiers.",
@@ -3911,7 +3953,7 @@ var suffixSubjectsRule = ruleCreator({
3911
3953
  "ObjectExpression > Property[computed=false] > Identifier": (node) => {
3912
3954
  if (validate.properties) {
3913
3955
  const parent = node.parent;
3914
- if (node === parent.key) {
3956
+ if (node === parent.key && !isSourcesObjectAcceptingStaticObservableCreator(parent.parent.parent)) {
3915
3957
  checkNode(node);
3916
3958
  }
3917
3959
  }
@@ -3976,12 +4018,12 @@ var suffixSubjectsRule = ruleCreator({
3976
4018
  // src/rules/throw-error.ts
3977
4019
  import { ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
3978
4020
  import * as tsutils4 from "ts-api-utils";
3979
- var defaultOptions17 = [];
4021
+ var defaultOptions18 = [];
3980
4022
  var throwErrorRule = ruleCreator({
3981
- defaultOptions: defaultOptions17,
4023
+ defaultOptions: defaultOptions18,
3982
4024
  meta: {
3983
4025
  docs: {
3984
- description: "Enforce passing only `Error` values to `throwError`.",
4026
+ description: "Enforce passing only `Error` values to `throwError` or `Subject.error`.",
3985
4027
  recommended: {
3986
4028
  recommended: true,
3987
4029
  strict: [{ allowThrowingAny: false, allowThrowingUnknown: false }]
@@ -4005,14 +4047,14 @@ var throwErrorRule = ruleCreator({
4005
4047
  name: "throw-error",
4006
4048
  create: (context) => {
4007
4049
  const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils12.getParserServices(context);
4008
- const { couldBeObservable } = getTypeServices(context);
4050
+ const { couldBeObservable, couldBeSubject } = getTypeServices(context);
4009
4051
  const [config = {}] = context.options;
4010
4052
  const { allowThrowingAny = true, allowThrowingUnknown = true } = config;
4011
- function checkThrowArgument(node) {
4053
+ function checkThrowArgument(node, noFunction = false) {
4012
4054
  var _a;
4013
4055
  let type = getTypeAtLocation(node);
4014
4056
  let reportNode = node;
4015
- if (couldBeFunction(type)) {
4057
+ if (!noFunction && couldBeFunction(type)) {
4016
4058
  reportNode = (_a = node.body) != null ? _a : node;
4017
4059
  const tsNode = esTreeNodeToTSNodeMap.get(node);
4018
4060
  const annotation = tsNode.type;
@@ -4033,7 +4075,7 @@ var throwErrorRule = ruleCreator({
4033
4075
  node: reportNode
4034
4076
  });
4035
4077
  }
4036
- function checkNode(node) {
4078
+ function checkThrowError(node) {
4037
4079
  if (couldBeObservable(node)) {
4038
4080
  const [arg] = node.arguments;
4039
4081
  if (arg) {
@@ -4041,13 +4083,18 @@ var throwErrorRule = ruleCreator({
4041
4083
  }
4042
4084
  }
4043
4085
  }
4044
- return {
4045
- "CallExpression[callee.name='throwError']": (node) => {
4046
- checkNode(node);
4047
- },
4048
- "CallExpression[callee.property.name='throwError']": (node) => {
4049
- checkNode(node);
4086
+ function checkSubjectError(node) {
4087
+ if (isMemberExpression(node.callee) && couldBeSubject(node.callee.object)) {
4088
+ const [arg] = node.arguments;
4089
+ if (arg) {
4090
+ checkThrowArgument(arg, true);
4091
+ }
4050
4092
  }
4093
+ }
4094
+ return {
4095
+ "CallExpression[callee.name='throwError']": checkThrowError,
4096
+ "CallExpression[callee.property.name='throwError']": checkThrowError,
4097
+ "CallExpression[callee.property.name='error']": checkSubjectError
4051
4098
  };
4052
4099
  }
4053
4100
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-rxjs-x",
3
3
  "type": "commonjs",
4
- "version": "0.7.3",
4
+ "version": "0.7.5",
5
5
  "packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
6
6
  "description": "ESLint v9+ plugin for RxJS",
7
7
  "author": "Jason Weinzierl <weinzierljason@gmail.com>",
@@ -72,28 +72,29 @@
72
72
  }
73
73
  },
74
74
  "devDependencies": {
75
- "@eslint/js": "^9.27.0",
75
+ "@eslint/js": "^9.28.0",
76
76
  "@stylistic/eslint-plugin": "^4.4.0",
77
77
  "@types/common-tags": "^1.8.4",
78
78
  "@types/node": "~18.18.0",
79
- "@typescript-eslint/rule-tester": "^8.32.1",
79
+ "@typescript-eslint/rule-tester": "^8.33.1",
80
80
  "@typescript/vfs": "^1.6.1",
81
- "@vitest/coverage-v8": "^3.1.4",
81
+ "@vitest/coverage-v8": "^3.2.0",
82
82
  "@vitest/eslint-plugin": "^1.2.1",
83
83
  "bumpp": "^10.1.1",
84
- "eslint": "^9.27.0",
84
+ "eslint": "^9.28.0",
85
85
  "eslint-config-flat-gitignore": "^2.1.0",
86
86
  "eslint-doc-generator": "^2.1.2",
87
- "eslint-import-resolver-typescript": "^4.4.0",
87
+ "eslint-import-resolver-typescript": "^4.4.2",
88
88
  "eslint-plugin-eslint-plugin": "^6.4.0",
89
- "eslint-plugin-import-x": "^4.13.1",
90
- "eslint-plugin-n": "^17.18.0",
89
+ "eslint-plugin-import-x": "^4.15.0",
90
+ "eslint-plugin-n": "^17.19.0",
91
91
  "markdownlint-cli2": "^0.18.1",
92
92
  "rxjs": "^7.8.2",
93
93
  "tsup": "^8.5.0",
94
94
  "typescript": "~5.8.3",
95
- "typescript-eslint": "^8.32.1",
96
- "vitest": "^3.1.4"
95
+ "typescript-eslint": "^8.33.1",
96
+ "vite": "^6.3.5",
97
+ "vitest": "^3.2.0"
97
98
  },
98
99
  "engines": {
99
100
  "node": "^18.18.0 || ^20.9.0 || >= 21.1.0"