eslint-plugin-rxjs-x 0.3.0 → 0.3.2

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
@@ -72,7 +72,7 @@ The package includes the following rules.
72
72
  | [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | 🔧 | | | |
73
73
  | [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | 🔧 | | | ❌ |
74
74
  | [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | ✅ | | | 💭 | |
75
- | [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | |
75
+ | [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | ||
76
76
  | [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | 💭 | |
77
77
  | [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | ✅ | | | 💭 | |
78
78
  | [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | 💭 | |
@@ -98,7 +98,7 @@ The package includes the following rules.
98
98
  | [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | 💭 | |
99
99
  | [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | 💭 | |
100
100
  | [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ |
101
- | [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | | 💭 | |
101
+ | [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | 💡 | 💭 | |
102
102
  | [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | ✅ | | | 💭 | |
103
103
  | [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | 💭 | |
104
104
  | [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | 💭 | |
@@ -106,6 +106,7 @@ The package includes the following rules.
106
106
  | [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | 💭 | |
107
107
  | [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | ✅ | | | 💭 | |
108
108
  | [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | 🔧 | 💡 | 💭 | |
109
+ | [prefer-root-operators](docs/rules/prefer-root-operators.md) | Disallow importing operators from `rxjs/operators`. | | 🔧 | 💡 | | |
109
110
  | [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | 💭 | |
110
111
  | [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | 💭 | |
111
112
 
package/dist/index.d.mts CHANGED
@@ -87,7 +87,7 @@ declare const rxjsX: {
87
87
  'no-subject-value': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
88
88
  'no-subscribe-handlers': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
89
89
  'no-tap': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
90
- 'no-topromise': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
90
+ 'no-topromise': TSESLint.RuleModule<"forbidden" | "suggestLastValueFrom" | "suggestFirstValueFrom", [], RxjsXRuleDocs, TSESLint.RuleListener>;
91
91
  'no-unbound-methods': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
92
92
  'no-unsafe-catch': TSESLint.RuleModule<"forbidden", readonly {
93
93
  observable?: string;
@@ -108,6 +108,7 @@ declare const rxjsX: {
108
108
  'prefer-observer': TSESLint.RuleModule<"forbidden", readonly {
109
109
  allowNext?: boolean;
110
110
  }[], RxjsXRuleDocs, TSESLint.RuleListener>;
111
+ 'prefer-root-operators': TSESLint.RuleModule<"forbidden" | "suggest" | "forbiddenWithoutFix", [], RxjsXRuleDocs, TSESLint.RuleListener>;
111
112
  'suffix-subjects': TSESLint.RuleModule<"forbidden", readonly {
112
113
  parameters?: boolean;
113
114
  properties?: boolean;
package/dist/index.d.ts CHANGED
@@ -87,7 +87,7 @@ declare const rxjsX: {
87
87
  'no-subject-value': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
88
88
  'no-subscribe-handlers': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
89
89
  'no-tap': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
90
- 'no-topromise': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
90
+ 'no-topromise': TSESLint.RuleModule<"forbidden" | "suggestLastValueFrom" | "suggestFirstValueFrom", [], RxjsXRuleDocs, TSESLint.RuleListener>;
91
91
  'no-unbound-methods': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
92
92
  'no-unsafe-catch': TSESLint.RuleModule<"forbidden", readonly {
93
93
  observable?: string;
@@ -108,6 +108,7 @@ declare const rxjsX: {
108
108
  'prefer-observer': TSESLint.RuleModule<"forbidden", readonly {
109
109
  allowNext?: boolean;
110
110
  }[], RxjsXRuleDocs, TSESLint.RuleListener>;
111
+ 'prefer-root-operators': TSESLint.RuleModule<"forbidden" | "suggest" | "forbiddenWithoutFix", [], RxjsXRuleDocs, TSESLint.RuleListener>;
111
112
  'suffix-subjects': TSESLint.RuleModule<"forbidden", readonly {
112
113
  parameters?: boolean;
113
114
  properties?: boolean;
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.3.0";
3
+ var version = "0.3.2";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -265,6 +265,15 @@ function isFunctionExpression(node) {
265
265
  function isIdentifier(node) {
266
266
  return node.type === _utils.AST_NODE_TYPES.Identifier;
267
267
  }
268
+ function isImportDeclaration(node) {
269
+ return node.type === _utils.AST_NODE_TYPES.ImportDeclaration;
270
+ }
271
+ function isImportNamespaceSpecifier(node) {
272
+ return node.type === _utils.AST_NODE_TYPES.ImportNamespaceSpecifier;
273
+ }
274
+ function isImportSpecifier(node) {
275
+ return node.type === _utils.AST_NODE_TYPES.ImportSpecifier;
276
+ }
268
277
  function isLiteral(node) {
269
278
  return node.type === _utils.AST_NODE_TYPES.Literal;
270
279
  }
@@ -814,6 +823,7 @@ var noAsyncSubscribeRule = ruleCreator({
814
823
  var noCompatRule = ruleCreator({
815
824
  defaultOptions: [],
816
825
  meta: {
826
+ deprecated: true,
817
827
  docs: {
818
828
  description: "Disallow the `rxjs-compat` package."
819
829
  },
@@ -1370,6 +1380,7 @@ var noIgnoredDefaultValueRule = ruleCreator({
1370
1380
  });
1371
1381
 
1372
1382
  // src/rules/no-ignored-error.ts
1383
+
1373
1384
  var noIgnoredErrorRule = ruleCreator({
1374
1385
  defaultOptions: [],
1375
1386
  meta: {
@@ -1385,12 +1396,44 @@ var noIgnoredErrorRule = ruleCreator({
1385
1396
  },
1386
1397
  name: "no-ignored-error",
1387
1398
  create: (context) => {
1399
+ const { getTypeAtLocation } = _utils.ESLintUtils.getParserServices(context);
1388
1400
  const { couldBeObservable, couldBeFunction: couldBeFunction2 } = getTypeServices(context);
1401
+ function isMissingErrorCallback(callExpression) {
1402
+ if (callExpression.arguments.length >= 2) {
1403
+ return false;
1404
+ }
1405
+ return couldBeFunction2(callExpression.arguments[0]);
1406
+ }
1407
+ function isObjMissingError(arg) {
1408
+ return !arg.properties.some(
1409
+ (property) => isProperty(property) && isIdentifier(property.key) && property.key.name === "error"
1410
+ );
1411
+ }
1412
+ function isTypeMissingError(arg) {
1413
+ const argType = getTypeAtLocation(arg);
1414
+ return !(argType == null ? void 0 : argType.getProperties().some((p) => p.name === "error"));
1415
+ }
1416
+ function isMissingErrorProperty(callExpression) {
1417
+ if (callExpression.arguments.length !== 1) {
1418
+ return false;
1419
+ }
1420
+ const [arg] = callExpression.arguments;
1421
+ if (isObjectExpression(arg)) {
1422
+ return isObjMissingError(arg);
1423
+ }
1424
+ if (isIdentifier(arg)) {
1425
+ return isTypeMissingError(arg);
1426
+ }
1427
+ if (isMemberExpression(arg) && isIdentifier(arg.property)) {
1428
+ return isTypeMissingError(arg.property);
1429
+ }
1430
+ return false;
1431
+ }
1389
1432
  return {
1390
1433
  "CallExpression[arguments.length > 0] > MemberExpression > Identifier[name='subscribe']": (node) => {
1391
1434
  const memberExpression = node.parent;
1392
1435
  const callExpression = memberExpression.parent;
1393
- if (callExpression.arguments.length < 2 && couldBeObservable(memberExpression.object) && couldBeFunction2(callExpression.arguments[0])) {
1436
+ if ((isMissingErrorCallback(callExpression) || isMissingErrorProperty(callExpression)) && couldBeObservable(memberExpression.object)) {
1394
1437
  context.report({
1395
1438
  messageId: "forbidden",
1396
1439
  node
@@ -2294,8 +2337,11 @@ var noTopromiseRule = ruleCreator({
2294
2337
  description: "Disallow use of the `toPromise` method.",
2295
2338
  requiresTypeChecking: true
2296
2339
  },
2340
+ hasSuggestions: true,
2297
2341
  messages: {
2298
- forbidden: "The toPromise method is forbidden."
2342
+ forbidden: "The toPromise method is forbidden.",
2343
+ suggestLastValueFrom: "Use lastValueFrom instead.",
2344
+ suggestFirstValueFrom: "Use firstValueFrom instead."
2299
2345
  },
2300
2346
  schema: [],
2301
2347
  type: "problem"
@@ -2303,14 +2349,74 @@ var noTopromiseRule = ruleCreator({
2303
2349
  name: "no-topromise",
2304
2350
  create: (context) => {
2305
2351
  const { couldBeObservable } = getTypeServices(context);
2352
+ function getQuote(raw) {
2353
+ const match = /^\s*('|")/.exec(raw);
2354
+ if (!match) {
2355
+ return void 0;
2356
+ }
2357
+ const [, quote] = match;
2358
+ return quote;
2359
+ }
2360
+ function createFix(conversion, callExpression, observableNode, importDeclarations) {
2361
+ return function* fix(fixer) {
2362
+ var _a, _b, _c;
2363
+ let namespace = "";
2364
+ let functionName = conversion;
2365
+ const rxjsImportDeclaration = importDeclarations.find((node) => node.source.value === "rxjs");
2366
+ if ((_a = rxjsImportDeclaration == null ? void 0 : rxjsImportDeclaration.specifiers) == null ? void 0 : _a.every(isImportNamespaceSpecifier)) {
2367
+ namespace = rxjsImportDeclaration.specifiers[0].local.name + ".";
2368
+ } else if ((_b = rxjsImportDeclaration == null ? void 0 : rxjsImportDeclaration.specifiers) == null ? void 0 : _b.every(isImportSpecifier)) {
2369
+ const { specifiers } = rxjsImportDeclaration;
2370
+ const existingSpecifier = specifiers.find((node) => (isIdentifier(node.imported) ? node.imported.name : node.imported.value) === functionName);
2371
+ if (existingSpecifier) {
2372
+ functionName = existingSpecifier.local.name;
2373
+ } else {
2374
+ const lastSpecifier = specifiers[specifiers.length - 1];
2375
+ yield fixer.insertTextAfter(lastSpecifier, `, ${functionName}`);
2376
+ }
2377
+ } else if (importDeclarations.length) {
2378
+ const lastImport = importDeclarations[importDeclarations.length - 1];
2379
+ const quote = (_c = getQuote(lastImport.source.raw)) != null ? _c : '"';
2380
+ yield fixer.insertTextAfter(
2381
+ importDeclarations[importDeclarations.length - 1],
2382
+ `
2383
+ import { ${functionName} } from ${quote}rxjs${quote};`
2384
+ );
2385
+ } else {
2386
+ console.warn("No import declarations found. Unable to suggest a fix.");
2387
+ return;
2388
+ }
2389
+ yield fixer.replaceText(
2390
+ callExpression,
2391
+ `${namespace}${functionName}(${context.sourceCode.getText(observableNode)})`
2392
+ );
2393
+ };
2394
+ }
2306
2395
  return {
2307
- [`MemberExpression[property.name="toPromise"]`]: (node) => {
2308
- if (couldBeObservable(node.object)) {
2309
- context.report({
2310
- messageId: "forbidden",
2311
- node: node.property
2312
- });
2396
+ [`CallExpression[callee.property.name="toPromise"]`]: (node) => {
2397
+ const memberExpression = node.callee;
2398
+ if (!couldBeObservable(memberExpression.object)) {
2399
+ return;
2400
+ }
2401
+ const { body } = context.sourceCode.ast;
2402
+ const importDeclarations = body.filter(isImportDeclaration);
2403
+ if (!importDeclarations.length) {
2404
+ return;
2313
2405
  }
2406
+ context.report({
2407
+ messageId: "forbidden",
2408
+ node: memberExpression.property,
2409
+ suggest: [
2410
+ {
2411
+ messageId: "suggestLastValueFrom",
2412
+ fix: createFix("lastValueFrom", node, memberExpression.object, importDeclarations)
2413
+ },
2414
+ {
2415
+ messageId: "suggestFirstValueFrom",
2416
+ fix: createFix("firstValueFrom", node, memberExpression.object, importDeclarations)
2417
+ }
2418
+ ]
2419
+ });
2314
2420
  }
2315
2421
  };
2316
2422
  }
@@ -2894,6 +3000,142 @@ function isValidArgText(argText) {
2894
3000
  return argText && argText !== "undefined" && argText !== "null";
2895
3001
  }
2896
3002
 
3003
+ // src/rules/prefer-root-operators.ts
3004
+ var RENAMED_OPERATORS = {
3005
+ combineLatest: "combineLatestWith",
3006
+ concat: "concatWith",
3007
+ merge: "mergeWith",
3008
+ onErrorResumeNext: "onErrorResumeNextWith",
3009
+ race: "raceWith",
3010
+ zip: "zipWith"
3011
+ };
3012
+ var DEPRECATED_OPERATORS = [
3013
+ "partition"
3014
+ ];
3015
+ var preferRootOperatorsRule = ruleCreator({
3016
+ defaultOptions: [],
3017
+ meta: {
3018
+ docs: {
3019
+ description: "Disallow importing operators from `rxjs/operators`."
3020
+ },
3021
+ fixable: "code",
3022
+ hasSuggestions: true,
3023
+ messages: {
3024
+ forbidden: "RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead.",
3025
+ forbiddenWithoutFix: "RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead. Note some operators may have been renamed or deprecated.",
3026
+ suggest: "Replace with import from `rxjs`."
3027
+ },
3028
+ schema: [],
3029
+ type: "suggestion"
3030
+ },
3031
+ name: "prefer-root-operators",
3032
+ create: (context) => {
3033
+ function getQuote(raw) {
3034
+ const match = /^\s*('|")/.exec(raw);
3035
+ if (!match) {
3036
+ return void 0;
3037
+ }
3038
+ const [, quote] = match;
3039
+ return quote;
3040
+ }
3041
+ function getSourceReplacement(rawLocation) {
3042
+ const quote = getQuote(rawLocation);
3043
+ if (!quote) {
3044
+ return void 0;
3045
+ }
3046
+ if (/^['"]rxjs\/operators/.test(rawLocation)) {
3047
+ return `${quote}rxjs${quote}`;
3048
+ }
3049
+ return void 0;
3050
+ }
3051
+ function hasDeprecatedOperators(specifiers) {
3052
+ return !!(specifiers == null ? void 0 : specifiers.some((s) => DEPRECATED_OPERATORS.includes(getName(getOperatorNode(s)))));
3053
+ }
3054
+ function getName(node) {
3055
+ return isIdentifier(node) ? node.name : node.value;
3056
+ }
3057
+ function getOperatorNode(node) {
3058
+ return isImportSpecifier(node) ? node.imported : node.local;
3059
+ }
3060
+ function getAliasNode(node) {
3061
+ return isImportSpecifier(node) ? node.local : node.exported;
3062
+ }
3063
+ function getOperatorReplacement(name2) {
3064
+ return RENAMED_OPERATORS[name2];
3065
+ }
3066
+ function isNodesEqual(a, b) {
3067
+ return a.range[0] === b.range[0] && a.range[1] === b.range[1];
3068
+ }
3069
+ function createFix(source, replacement, specifiers) {
3070
+ return function* fix(fixer) {
3071
+ yield fixer.replaceText(source, replacement);
3072
+ for (const specifier of specifiers) {
3073
+ const operatorNode = getOperatorNode(specifier);
3074
+ const operatorName = getName(operatorNode);
3075
+ const operatorReplacement = getOperatorReplacement(operatorName);
3076
+ if (!operatorReplacement) {
3077
+ continue;
3078
+ }
3079
+ const aliasNode = getAliasNode(specifier);
3080
+ if (isNodesEqual(aliasNode, operatorNode)) {
3081
+ yield fixer.insertTextBefore(operatorNode, operatorReplacement + " as ");
3082
+ } else if (isIdentifier(operatorNode)) {
3083
+ yield fixer.replaceText(operatorNode, operatorReplacement);
3084
+ } else {
3085
+ const quote = getQuote(operatorNode.raw);
3086
+ if (!quote) {
3087
+ continue;
3088
+ }
3089
+ yield fixer.replaceText(operatorNode, quote + operatorReplacement + quote);
3090
+ }
3091
+ }
3092
+ };
3093
+ }
3094
+ function reportNode(source, specifiers) {
3095
+ const replacement = getSourceReplacement(source.raw);
3096
+ if (!replacement || hasDeprecatedOperators(specifiers)) {
3097
+ context.report({
3098
+ messageId: "forbiddenWithoutFix",
3099
+ node: source
3100
+ });
3101
+ return;
3102
+ }
3103
+ if (!specifiers) {
3104
+ context.report({
3105
+ messageId: "forbiddenWithoutFix",
3106
+ node: source,
3107
+ suggest: [{ messageId: "suggest", fix: (fixer) => fixer.replaceText(source, replacement) }]
3108
+ });
3109
+ return;
3110
+ }
3111
+ const fix = createFix(source, replacement, specifiers);
3112
+ context.report({
3113
+ fix,
3114
+ messageId: "forbidden",
3115
+ node: source,
3116
+ suggest: [{ messageId: "suggest", fix }]
3117
+ });
3118
+ }
3119
+ return {
3120
+ 'ImportDeclaration[source.value="rxjs/operators"]': (node) => {
3121
+ const specifiers = node.specifiers.length && node.specifiers.every((importClause) => isImportSpecifier(importClause)) ? node.specifiers : void 0;
3122
+ reportNode(node.source, specifiers);
3123
+ },
3124
+ 'ImportExpression[source.value="rxjs/operators"]': (node) => {
3125
+ if (isLiteral(node.source)) {
3126
+ reportNode(node.source);
3127
+ }
3128
+ },
3129
+ 'ExportNamedDeclaration[source.value="rxjs/operators"]': (node) => {
3130
+ reportNode(node.source, node.specifiers);
3131
+ },
3132
+ 'ExportAllDeclaration[source.value="rxjs/operators"]': (node) => {
3133
+ reportNode(node.source);
3134
+ }
3135
+ };
3136
+ }
3137
+ });
3138
+
2897
3139
  // src/rules/suffix-subjects.ts
2898
3140
 
2899
3141
  var defaultOptions13 = [];
@@ -3211,6 +3453,7 @@ var plugin = {
3211
3453
  "no-unsafe-switchmap": noUnsafeSwitchmapRule,
3212
3454
  "no-unsafe-takeuntil": noUnsafeTakeuntilRule,
3213
3455
  "prefer-observer": preferObserverRule,
3456
+ "prefer-root-operators": preferRootOperatorsRule,
3214
3457
  "suffix-subjects": suffixSubjectsRule,
3215
3458
  "throw-error": throwErrorRule
3216
3459
  }
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.3.0";
3
+ var version = "0.3.2";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -265,6 +265,15 @@ function isFunctionExpression(node) {
265
265
  function isIdentifier(node) {
266
266
  return node.type === AST_NODE_TYPES2.Identifier;
267
267
  }
268
+ function isImportDeclaration(node) {
269
+ return node.type === AST_NODE_TYPES2.ImportDeclaration;
270
+ }
271
+ function isImportNamespaceSpecifier(node) {
272
+ return node.type === AST_NODE_TYPES2.ImportNamespaceSpecifier;
273
+ }
274
+ function isImportSpecifier(node) {
275
+ return node.type === AST_NODE_TYPES2.ImportSpecifier;
276
+ }
268
277
  function isLiteral(node) {
269
278
  return node.type === AST_NODE_TYPES2.Literal;
270
279
  }
@@ -814,6 +823,7 @@ var noAsyncSubscribeRule = ruleCreator({
814
823
  var noCompatRule = ruleCreator({
815
824
  defaultOptions: [],
816
825
  meta: {
826
+ deprecated: true,
817
827
  docs: {
818
828
  description: "Disallow the `rxjs-compat` package."
819
829
  },
@@ -1370,6 +1380,7 @@ var noIgnoredDefaultValueRule = ruleCreator({
1370
1380
  });
1371
1381
 
1372
1382
  // src/rules/no-ignored-error.ts
1383
+ import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
1373
1384
  var noIgnoredErrorRule = ruleCreator({
1374
1385
  defaultOptions: [],
1375
1386
  meta: {
@@ -1385,12 +1396,44 @@ var noIgnoredErrorRule = ruleCreator({
1385
1396
  },
1386
1397
  name: "no-ignored-error",
1387
1398
  create: (context) => {
1399
+ const { getTypeAtLocation } = ESLintUtils7.getParserServices(context);
1388
1400
  const { couldBeObservable, couldBeFunction: couldBeFunction2 } = getTypeServices(context);
1401
+ function isMissingErrorCallback(callExpression) {
1402
+ if (callExpression.arguments.length >= 2) {
1403
+ return false;
1404
+ }
1405
+ return couldBeFunction2(callExpression.arguments[0]);
1406
+ }
1407
+ function isObjMissingError(arg) {
1408
+ return !arg.properties.some(
1409
+ (property) => isProperty(property) && isIdentifier(property.key) && property.key.name === "error"
1410
+ );
1411
+ }
1412
+ function isTypeMissingError(arg) {
1413
+ const argType = getTypeAtLocation(arg);
1414
+ return !(argType == null ? void 0 : argType.getProperties().some((p) => p.name === "error"));
1415
+ }
1416
+ function isMissingErrorProperty(callExpression) {
1417
+ if (callExpression.arguments.length !== 1) {
1418
+ return false;
1419
+ }
1420
+ const [arg] = callExpression.arguments;
1421
+ if (isObjectExpression(arg)) {
1422
+ return isObjMissingError(arg);
1423
+ }
1424
+ if (isIdentifier(arg)) {
1425
+ return isTypeMissingError(arg);
1426
+ }
1427
+ if (isMemberExpression(arg) && isIdentifier(arg.property)) {
1428
+ return isTypeMissingError(arg.property);
1429
+ }
1430
+ return false;
1431
+ }
1389
1432
  return {
1390
1433
  "CallExpression[arguments.length > 0] > MemberExpression > Identifier[name='subscribe']": (node) => {
1391
1434
  const memberExpression = node.parent;
1392
1435
  const callExpression = memberExpression.parent;
1393
- if (callExpression.arguments.length < 2 && couldBeObservable(memberExpression.object) && couldBeFunction2(callExpression.arguments[0])) {
1436
+ if ((isMissingErrorCallback(callExpression) || isMissingErrorProperty(callExpression)) && couldBeObservable(memberExpression.object)) {
1394
1437
  context.report({
1395
1438
  messageId: "forbidden",
1396
1439
  node
@@ -2294,8 +2337,11 @@ var noTopromiseRule = ruleCreator({
2294
2337
  description: "Disallow use of the `toPromise` method.",
2295
2338
  requiresTypeChecking: true
2296
2339
  },
2340
+ hasSuggestions: true,
2297
2341
  messages: {
2298
- forbidden: "The toPromise method is forbidden."
2342
+ forbidden: "The toPromise method is forbidden.",
2343
+ suggestLastValueFrom: "Use lastValueFrom instead.",
2344
+ suggestFirstValueFrom: "Use firstValueFrom instead."
2299
2345
  },
2300
2346
  schema: [],
2301
2347
  type: "problem"
@@ -2303,21 +2349,81 @@ var noTopromiseRule = ruleCreator({
2303
2349
  name: "no-topromise",
2304
2350
  create: (context) => {
2305
2351
  const { couldBeObservable } = getTypeServices(context);
2352
+ function getQuote(raw) {
2353
+ const match = /^\s*('|")/.exec(raw);
2354
+ if (!match) {
2355
+ return void 0;
2356
+ }
2357
+ const [, quote] = match;
2358
+ return quote;
2359
+ }
2360
+ function createFix(conversion, callExpression, observableNode, importDeclarations) {
2361
+ return function* fix(fixer) {
2362
+ var _a, _b, _c;
2363
+ let namespace = "";
2364
+ let functionName = conversion;
2365
+ const rxjsImportDeclaration = importDeclarations.find((node) => node.source.value === "rxjs");
2366
+ if ((_a = rxjsImportDeclaration == null ? void 0 : rxjsImportDeclaration.specifiers) == null ? void 0 : _a.every(isImportNamespaceSpecifier)) {
2367
+ namespace = rxjsImportDeclaration.specifiers[0].local.name + ".";
2368
+ } else if ((_b = rxjsImportDeclaration == null ? void 0 : rxjsImportDeclaration.specifiers) == null ? void 0 : _b.every(isImportSpecifier)) {
2369
+ const { specifiers } = rxjsImportDeclaration;
2370
+ const existingSpecifier = specifiers.find((node) => (isIdentifier(node.imported) ? node.imported.name : node.imported.value) === functionName);
2371
+ if (existingSpecifier) {
2372
+ functionName = existingSpecifier.local.name;
2373
+ } else {
2374
+ const lastSpecifier = specifiers[specifiers.length - 1];
2375
+ yield fixer.insertTextAfter(lastSpecifier, `, ${functionName}`);
2376
+ }
2377
+ } else if (importDeclarations.length) {
2378
+ const lastImport = importDeclarations[importDeclarations.length - 1];
2379
+ const quote = (_c = getQuote(lastImport.source.raw)) != null ? _c : '"';
2380
+ yield fixer.insertTextAfter(
2381
+ importDeclarations[importDeclarations.length - 1],
2382
+ `
2383
+ import { ${functionName} } from ${quote}rxjs${quote};`
2384
+ );
2385
+ } else {
2386
+ console.warn("No import declarations found. Unable to suggest a fix.");
2387
+ return;
2388
+ }
2389
+ yield fixer.replaceText(
2390
+ callExpression,
2391
+ `${namespace}${functionName}(${context.sourceCode.getText(observableNode)})`
2392
+ );
2393
+ };
2394
+ }
2306
2395
  return {
2307
- [`MemberExpression[property.name="toPromise"]`]: (node) => {
2308
- if (couldBeObservable(node.object)) {
2309
- context.report({
2310
- messageId: "forbidden",
2311
- node: node.property
2312
- });
2396
+ [`CallExpression[callee.property.name="toPromise"]`]: (node) => {
2397
+ const memberExpression = node.callee;
2398
+ if (!couldBeObservable(memberExpression.object)) {
2399
+ return;
2313
2400
  }
2401
+ const { body } = context.sourceCode.ast;
2402
+ const importDeclarations = body.filter(isImportDeclaration);
2403
+ if (!importDeclarations.length) {
2404
+ return;
2405
+ }
2406
+ context.report({
2407
+ messageId: "forbidden",
2408
+ node: memberExpression.property,
2409
+ suggest: [
2410
+ {
2411
+ messageId: "suggestLastValueFrom",
2412
+ fix: createFix("lastValueFrom", node, memberExpression.object, importDeclarations)
2413
+ },
2414
+ {
2415
+ messageId: "suggestFirstValueFrom",
2416
+ fix: createFix("firstValueFrom", node, memberExpression.object, importDeclarations)
2417
+ }
2418
+ ]
2419
+ });
2314
2420
  }
2315
2421
  };
2316
2422
  }
2317
2423
  });
2318
2424
 
2319
2425
  // src/rules/no-unbound-methods.ts
2320
- import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
2426
+ import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
2321
2427
  var noUnboundMethodsRule = ruleCreator({
2322
2428
  defaultOptions: [],
2323
2429
  meta: {
@@ -2334,7 +2440,7 @@ var noUnboundMethodsRule = ruleCreator({
2334
2440
  },
2335
2441
  name: "no-unbound-methods",
2336
2442
  create: (context) => {
2337
- const { getTypeAtLocation } = ESLintUtils7.getParserServices(context);
2443
+ const { getTypeAtLocation } = ESLintUtils8.getParserServices(context);
2338
2444
  const { couldBeObservable, couldBeSubscription } = getTypeServices(context);
2339
2445
  const nodeMap = /* @__PURE__ */ new WeakMap();
2340
2446
  function mapArguments(node) {
@@ -2512,7 +2618,7 @@ var noUnsafeFirstRule = ruleCreator({
2512
2618
  });
2513
2619
 
2514
2620
  // src/rules/no-unsafe-subject-next.ts
2515
- import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
2621
+ import { ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
2516
2622
  import * as tsutils2 from "ts-api-utils";
2517
2623
  import ts6 from "typescript";
2518
2624
  var noUnsafeSubjectNext = ruleCreator({
@@ -2531,7 +2637,7 @@ var noUnsafeSubjectNext = ruleCreator({
2531
2637
  },
2532
2638
  name: "no-unsafe-subject-next",
2533
2639
  create: (context) => {
2534
- const { getTypeAtLocation, program } = ESLintUtils8.getParserServices(context);
2640
+ const { getTypeAtLocation, program } = ESLintUtils9.getParserServices(context);
2535
2641
  const typeChecker = program.getTypeChecker();
2536
2642
  return {
2537
2643
  [`CallExpression[callee.property.name='next']`]: (node) => {
@@ -2894,8 +3000,144 @@ function isValidArgText(argText) {
2894
3000
  return argText && argText !== "undefined" && argText !== "null";
2895
3001
  }
2896
3002
 
3003
+ // src/rules/prefer-root-operators.ts
3004
+ var RENAMED_OPERATORS = {
3005
+ combineLatest: "combineLatestWith",
3006
+ concat: "concatWith",
3007
+ merge: "mergeWith",
3008
+ onErrorResumeNext: "onErrorResumeNextWith",
3009
+ race: "raceWith",
3010
+ zip: "zipWith"
3011
+ };
3012
+ var DEPRECATED_OPERATORS = [
3013
+ "partition"
3014
+ ];
3015
+ var preferRootOperatorsRule = ruleCreator({
3016
+ defaultOptions: [],
3017
+ meta: {
3018
+ docs: {
3019
+ description: "Disallow importing operators from `rxjs/operators`."
3020
+ },
3021
+ fixable: "code",
3022
+ hasSuggestions: true,
3023
+ messages: {
3024
+ forbidden: "RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead.",
3025
+ forbiddenWithoutFix: "RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead. Note some operators may have been renamed or deprecated.",
3026
+ suggest: "Replace with import from `rxjs`."
3027
+ },
3028
+ schema: [],
3029
+ type: "suggestion"
3030
+ },
3031
+ name: "prefer-root-operators",
3032
+ create: (context) => {
3033
+ function getQuote(raw) {
3034
+ const match = /^\s*('|")/.exec(raw);
3035
+ if (!match) {
3036
+ return void 0;
3037
+ }
3038
+ const [, quote] = match;
3039
+ return quote;
3040
+ }
3041
+ function getSourceReplacement(rawLocation) {
3042
+ const quote = getQuote(rawLocation);
3043
+ if (!quote) {
3044
+ return void 0;
3045
+ }
3046
+ if (/^['"]rxjs\/operators/.test(rawLocation)) {
3047
+ return `${quote}rxjs${quote}`;
3048
+ }
3049
+ return void 0;
3050
+ }
3051
+ function hasDeprecatedOperators(specifiers) {
3052
+ return !!(specifiers == null ? void 0 : specifiers.some((s) => DEPRECATED_OPERATORS.includes(getName(getOperatorNode(s)))));
3053
+ }
3054
+ function getName(node) {
3055
+ return isIdentifier(node) ? node.name : node.value;
3056
+ }
3057
+ function getOperatorNode(node) {
3058
+ return isImportSpecifier(node) ? node.imported : node.local;
3059
+ }
3060
+ function getAliasNode(node) {
3061
+ return isImportSpecifier(node) ? node.local : node.exported;
3062
+ }
3063
+ function getOperatorReplacement(name2) {
3064
+ return RENAMED_OPERATORS[name2];
3065
+ }
3066
+ function isNodesEqual(a, b) {
3067
+ return a.range[0] === b.range[0] && a.range[1] === b.range[1];
3068
+ }
3069
+ function createFix(source, replacement, specifiers) {
3070
+ return function* fix(fixer) {
3071
+ yield fixer.replaceText(source, replacement);
3072
+ for (const specifier of specifiers) {
3073
+ const operatorNode = getOperatorNode(specifier);
3074
+ const operatorName = getName(operatorNode);
3075
+ const operatorReplacement = getOperatorReplacement(operatorName);
3076
+ if (!operatorReplacement) {
3077
+ continue;
3078
+ }
3079
+ const aliasNode = getAliasNode(specifier);
3080
+ if (isNodesEqual(aliasNode, operatorNode)) {
3081
+ yield fixer.insertTextBefore(operatorNode, operatorReplacement + " as ");
3082
+ } else if (isIdentifier(operatorNode)) {
3083
+ yield fixer.replaceText(operatorNode, operatorReplacement);
3084
+ } else {
3085
+ const quote = getQuote(operatorNode.raw);
3086
+ if (!quote) {
3087
+ continue;
3088
+ }
3089
+ yield fixer.replaceText(operatorNode, quote + operatorReplacement + quote);
3090
+ }
3091
+ }
3092
+ };
3093
+ }
3094
+ function reportNode(source, specifiers) {
3095
+ const replacement = getSourceReplacement(source.raw);
3096
+ if (!replacement || hasDeprecatedOperators(specifiers)) {
3097
+ context.report({
3098
+ messageId: "forbiddenWithoutFix",
3099
+ node: source
3100
+ });
3101
+ return;
3102
+ }
3103
+ if (!specifiers) {
3104
+ context.report({
3105
+ messageId: "forbiddenWithoutFix",
3106
+ node: source,
3107
+ suggest: [{ messageId: "suggest", fix: (fixer) => fixer.replaceText(source, replacement) }]
3108
+ });
3109
+ return;
3110
+ }
3111
+ const fix = createFix(source, replacement, specifiers);
3112
+ context.report({
3113
+ fix,
3114
+ messageId: "forbidden",
3115
+ node: source,
3116
+ suggest: [{ messageId: "suggest", fix }]
3117
+ });
3118
+ }
3119
+ return {
3120
+ 'ImportDeclaration[source.value="rxjs/operators"]': (node) => {
3121
+ const specifiers = node.specifiers.length && node.specifiers.every((importClause) => isImportSpecifier(importClause)) ? node.specifiers : void 0;
3122
+ reportNode(node.source, specifiers);
3123
+ },
3124
+ 'ImportExpression[source.value="rxjs/operators"]': (node) => {
3125
+ if (isLiteral(node.source)) {
3126
+ reportNode(node.source);
3127
+ }
3128
+ },
3129
+ 'ExportNamedDeclaration[source.value="rxjs/operators"]': (node) => {
3130
+ reportNode(node.source, node.specifiers);
3131
+ },
3132
+ 'ExportAllDeclaration[source.value="rxjs/operators"]': (node) => {
3133
+ reportNode(node.source);
3134
+ }
3135
+ };
3136
+ }
3137
+ });
3138
+
2897
3139
  // src/rules/suffix-subjects.ts
2898
- import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
3140
+ import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
2899
3141
  var defaultOptions13 = [];
2900
3142
  var suffixSubjectsRule = ruleCreator({
2901
3143
  defaultOptions: defaultOptions13,
@@ -2923,7 +3165,7 @@ var suffixSubjectsRule = ruleCreator({
2923
3165
  },
2924
3166
  name: "suffix-subjects",
2925
3167
  create: (context) => {
2926
- const { esTreeNodeToTSNodeMap } = ESLintUtils9.getParserServices(context);
3168
+ const { esTreeNodeToTSNodeMap } = ESLintUtils10.getParserServices(context);
2927
3169
  const { couldBeType: couldBeType2 } = getTypeServices(context);
2928
3170
  const [config = {}] = context.options;
2929
3171
  const validate = {
@@ -3093,7 +3335,7 @@ var suffixSubjectsRule = ruleCreator({
3093
3335
  });
3094
3336
 
3095
3337
  // src/rules/throw-error.ts
3096
- import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
3338
+ import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
3097
3339
  import * as tsutils3 from "ts-api-utils";
3098
3340
  var defaultOptions14 = [];
3099
3341
  var throwErrorRule = ruleCreator({
@@ -3119,7 +3361,7 @@ var throwErrorRule = ruleCreator({
3119
3361
  },
3120
3362
  name: "throw-error",
3121
3363
  create: (context) => {
3122
- const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils10.getParserServices(context);
3364
+ const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils11.getParserServices(context);
3123
3365
  const { couldBeObservable } = getTypeServices(context);
3124
3366
  const [config = {}] = context.options;
3125
3367
  const { allowThrowingAny = true, allowThrowingUnknown = true } = config;
@@ -3211,6 +3453,7 @@ var plugin = {
3211
3453
  "no-unsafe-switchmap": noUnsafeSwitchmapRule,
3212
3454
  "no-unsafe-takeuntil": noUnsafeTakeuntilRule,
3213
3455
  "prefer-observer": preferObserverRule,
3456
+ "prefer-root-operators": preferRootOperatorsRule,
3214
3457
  "suffix-subjects": suffixSubjectsRule,
3215
3458
  "throw-error": throwErrorRule
3216
3459
  }
@@ -7,6 +7,9 @@
7
7
  <!-- end auto-generated rule header -->
8
8
 
9
9
  This rule effects failures if async functions are passed to `subscribe`.
10
+ Developers are encouraged to avoid race conditions
11
+ by instead using RxJS operators which can handle both Promises and Observables
12
+ (e.g. `concatMap`, `switchMap`, `mergeMap`, `exhaustMap`).
10
13
 
11
14
  ## Rule details
12
15
 
@@ -14,16 +17,26 @@ Examples of **incorrect** code for this rule:
14
17
 
15
18
  ```ts
16
19
  import { of } from "rxjs";
17
- of(42).subscribe(async () => console.log(value));
20
+
21
+ of(42).subscribe(async value => {
22
+ const data1 = await fetch(`https://api.some.com/things/${value}`);
23
+ const data2 = await fetch(`https://api.some.com/things/${data1.id}`);
24
+ console.log(data2);
25
+ });
18
26
  ```
19
27
 
20
28
  Examples of **correct** code for this rule:
21
29
 
22
30
  ```ts
23
31
  import { of } from "rxjs";
24
- of(42).subscribe(() => console.log(value));
32
+
33
+ of(42).pipe(
34
+ switchMap(value => fetch(`http://api.some.com/things/${value}`)),
35
+ switchMap(data1 => fetch(`http://api.some.com/things/${data1.id}`)),
36
+ ).subscribe(data2 => console.log(data2));
25
37
  ```
26
38
 
27
39
  ## Further reading
28
40
 
29
41
  - [Why does this rule exist?](https://stackoverflow.com/q/71559135)
42
+ - [Higher-order Observables](https://rxjs.dev/guide/higher-order-observables)
@@ -1,9 +1,13 @@
1
1
  # Disallow the `rxjs-compat` package (`rxjs-x/no-compat`)
2
2
 
3
+ ❌ This rule is deprecated.
4
+
3
5
  <!-- end auto-generated rule header -->
4
6
 
5
7
  This rule prevents importing from locations that depend upon the `rxjs-compat` package.
6
8
 
9
+ This rule is deprecated because RxJS v7+ no longer includes `rxjs-compat`.
10
+
7
11
  ## Further reading
8
12
 
9
13
  - [Backwards compatibility](https://github.com/ReactiveX/rxjs/blob/a6590e971969c736a15b77154dabbc22275aa0d5/docs_app/content/guide/v6/migration.md#backwards-compatibility)
@@ -1,7 +1,13 @@
1
1
  # Disallow use of the `toPromise` method (`rxjs-x/no-topromise`)
2
2
 
3
+ 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
4
+
3
5
  💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting).
4
6
 
5
7
  <!-- end auto-generated rule header -->
6
8
 
7
9
  This rule effects failures if the `toPromise` method is used.
10
+
11
+ ## Further reading
12
+
13
+ - [Conversion to Promises](https://rxjs.dev/deprecations/to-promise)
@@ -0,0 +1,32 @@
1
+ # Disallow importing operators from `rxjs/operators` (`rxjs-x/prefer-root-operators`)
2
+
3
+ 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ This rule prevents importing from the `rxjs/operators` export site.
8
+ Most operators were moved to the `rxjs` export site in RxJS v7.2.0
9
+ (excepting a few old and deprecated operators).
10
+ The `rxjs/operators` export site has since been deprecated and will be removed in a future major version.
11
+
12
+ Note that because a few operators were renamed or not migrated to the `rxjs` export site,
13
+ this rule may not provide an automatic fixer if renaming the import path is not guaranteed to be safe.
14
+ See the documentation linked below.
15
+
16
+ ## Rule details
17
+
18
+ Examples of **incorrect** code for this rule:
19
+
20
+ ```ts
21
+ import { map } from 'rxjs/operators';
22
+ ```
23
+
24
+ Examples of **correct** code for this rule:
25
+
26
+ ```ts
27
+ import { map } from 'rxjs';
28
+ ```
29
+
30
+ ## Further reading
31
+
32
+ - [Importing instructions](https://rxjs.dev/guide/importing)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-rxjs-x",
3
3
  "type": "commonjs",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "packageManager": "yarn@4.5.1+sha512.341db9396b6e289fecc30cd7ab3af65060e05ebff4b3b47547b278b9e67b08f485ecd8c79006b405446262142c7a38154445ef7f17c1d5d1de7d90bf9ce7054d",
6
6
  "description": "ESLint v9+ plugin for RxJS",
7
7
  "author": "Jason Weinzierl <weinzierljason@gmail.com>",
@@ -84,13 +84,12 @@
84
84
  "eslint-config-flat-gitignore": "^0.3.0",
85
85
  "eslint-doc-generator": "^1.7.1",
86
86
  "eslint-import-resolver-typescript": "^3.6.3",
87
- "eslint-plugin-eslint-plugin": "^6.3.1",
87
+ "eslint-plugin-eslint-plugin": "^6.3.2",
88
88
  "eslint-plugin-import-x": "^4.4.2",
89
- "eslint-plugin-n": "^17.13.1",
89
+ "eslint-plugin-n": "^17.13.2",
90
90
  "markdownlint-cli2": "^0.15.0",
91
91
  "rxjs": "^7.8.1",
92
92
  "tsup": "^8.3.5",
93
- "tsx": "^4.19.2",
94
93
  "typescript": "~5.6.3",
95
94
  "typescript-eslint": "^8.14.0",
96
95
  "vitest": "^2.1.5"