eslint-plugin-rxjs-x 0.3.1 → 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 +3 -2
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +218 -8
- package/dist/index.mjs +218 -8
- package/docs/rules/no-compat.md +4 -0
- package/docs/rules/no-topromise.md +6 -0
- package/docs/rules/prefer-root-operators.md +32 -0
- package/package.json +3 -3
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.
|
|
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
|
},
|
|
@@ -2327,8 +2337,11 @@ var noTopromiseRule = ruleCreator({
|
|
|
2327
2337
|
description: "Disallow use of the `toPromise` method.",
|
|
2328
2338
|
requiresTypeChecking: true
|
|
2329
2339
|
},
|
|
2340
|
+
hasSuggestions: true,
|
|
2330
2341
|
messages: {
|
|
2331
|
-
forbidden: "The toPromise method is forbidden."
|
|
2342
|
+
forbidden: "The toPromise method is forbidden.",
|
|
2343
|
+
suggestLastValueFrom: "Use lastValueFrom instead.",
|
|
2344
|
+
suggestFirstValueFrom: "Use firstValueFrom instead."
|
|
2332
2345
|
},
|
|
2333
2346
|
schema: [],
|
|
2334
2347
|
type: "problem"
|
|
@@ -2336,14 +2349,74 @@ var noTopromiseRule = ruleCreator({
|
|
|
2336
2349
|
name: "no-topromise",
|
|
2337
2350
|
create: (context) => {
|
|
2338
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
|
+
}
|
|
2339
2395
|
return {
|
|
2340
|
-
[`
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
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;
|
|
2346
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
|
+
});
|
|
2347
2420
|
}
|
|
2348
2421
|
};
|
|
2349
2422
|
}
|
|
@@ -2927,6 +3000,142 @@ function isValidArgText(argText) {
|
|
|
2927
3000
|
return argText && argText !== "undefined" && argText !== "null";
|
|
2928
3001
|
}
|
|
2929
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
|
+
|
|
2930
3139
|
// src/rules/suffix-subjects.ts
|
|
2931
3140
|
|
|
2932
3141
|
var defaultOptions13 = [];
|
|
@@ -3244,6 +3453,7 @@ var plugin = {
|
|
|
3244
3453
|
"no-unsafe-switchmap": noUnsafeSwitchmapRule,
|
|
3245
3454
|
"no-unsafe-takeuntil": noUnsafeTakeuntilRule,
|
|
3246
3455
|
"prefer-observer": preferObserverRule,
|
|
3456
|
+
"prefer-root-operators": preferRootOperatorsRule,
|
|
3247
3457
|
"suffix-subjects": suffixSubjectsRule,
|
|
3248
3458
|
"throw-error": throwErrorRule
|
|
3249
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.
|
|
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
|
},
|
|
@@ -2327,8 +2337,11 @@ var noTopromiseRule = ruleCreator({
|
|
|
2327
2337
|
description: "Disallow use of the `toPromise` method.",
|
|
2328
2338
|
requiresTypeChecking: true
|
|
2329
2339
|
},
|
|
2340
|
+
hasSuggestions: true,
|
|
2330
2341
|
messages: {
|
|
2331
|
-
forbidden: "The toPromise method is forbidden."
|
|
2342
|
+
forbidden: "The toPromise method is forbidden.",
|
|
2343
|
+
suggestLastValueFrom: "Use lastValueFrom instead.",
|
|
2344
|
+
suggestFirstValueFrom: "Use firstValueFrom instead."
|
|
2332
2345
|
},
|
|
2333
2346
|
schema: [],
|
|
2334
2347
|
type: "problem"
|
|
@@ -2336,14 +2349,74 @@ var noTopromiseRule = ruleCreator({
|
|
|
2336
2349
|
name: "no-topromise",
|
|
2337
2350
|
create: (context) => {
|
|
2338
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
|
+
}
|
|
2339
2395
|
return {
|
|
2340
|
-
[`
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
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;
|
|
2346
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
|
+
});
|
|
2347
2420
|
}
|
|
2348
2421
|
};
|
|
2349
2422
|
}
|
|
@@ -2927,6 +3000,142 @@ function isValidArgText(argText) {
|
|
|
2927
3000
|
return argText && argText !== "undefined" && argText !== "null";
|
|
2928
3001
|
}
|
|
2929
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
|
+
|
|
2930
3139
|
// src/rules/suffix-subjects.ts
|
|
2931
3140
|
import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
|
|
2932
3141
|
var defaultOptions13 = [];
|
|
@@ -3244,6 +3453,7 @@ var plugin = {
|
|
|
3244
3453
|
"no-unsafe-switchmap": noUnsafeSwitchmapRule,
|
|
3245
3454
|
"no-unsafe-takeuntil": noUnsafeTakeuntilRule,
|
|
3246
3455
|
"prefer-observer": preferObserverRule,
|
|
3456
|
+
"prefer-root-operators": preferRootOperatorsRule,
|
|
3247
3457
|
"suffix-subjects": suffixSubjectsRule,
|
|
3248
3458
|
"throw-error": throwErrorRule
|
|
3249
3459
|
}
|
package/docs/rules/no-compat.md
CHANGED
|
@@ -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.
|
|
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,9 +84,9 @@
|
|
|
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.
|
|
87
|
+
"eslint-plugin-eslint-plugin": "^6.3.2",
|
|
88
88
|
"eslint-plugin-import-x": "^4.4.2",
|
|
89
|
-
"eslint-plugin-n": "^17.13.
|
|
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",
|