From 51ed5f6e6d897f153989fd47b6fa3ee1631ca29d Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Mon, 18 Nov 2024 11:58:57 -0600 Subject: [PATCH 1/7] feat(no-import-operators): new rule to ban rxjs/operators imports --- README.md | 1 + docs/rules/no-import-operators.md | 26 ++++++++ src/index.ts | 2 + src/rules/no-import-operators.ts | 45 ++++++++++++++ tests/rules/no-import-operators.test.ts | 82 +++++++++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 docs/rules/no-import-operators.md create mode 100644 src/rules/no-import-operators.ts create mode 100644 tests/rules/no-import-operators.test.ts diff --git a/README.md b/README.md index 4c0f50d9..ce22078a 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ The package includes the following rules. | [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | | [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | | [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | | | | | | [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | | [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | | [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | diff --git a/docs/rules/no-import-operators.md b/docs/rules/no-import-operators.md new file mode 100644 index 00000000..3ae60792 --- /dev/null +++ b/docs/rules/no-import-operators.md @@ -0,0 +1,26 @@ +# Disallow importing operators from `rxjs/operators` (`rxjs-x/no-import-operators`) + + + +This rule prevents importing from the `rxjs/operators` export site. +Most operators were moved to the `rxjs` export site in RxJS v7.2.0 +(excepting a couple of old and deprecated operators). +The `rxjs/operators` export site has since been deprecated and will be removed in a future major version. + +## Rule details + +Examples of **incorrect** code for this rule: + +```ts +import { map } from 'rxjs/operators'; +``` + +Examples of **correct** code for this rule: + +```ts +import { map } from 'rxjs'; +``` + +## Further reading + +- [Importing instructions](https://rxjs.dev/guide/importing) diff --git a/src/index.ts b/src/index.ts index fce1f0b6..c20524de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ import { noIgnoredSubscribeRule } from './rules/no-ignored-subscribe'; import { noIgnoredSubscriptionRule } from './rules/no-ignored-subscription'; import { noIgnoredTakewhileValueRule } from './rules/no-ignored-takewhile-value'; import { noImplicitAnyCatchRule } from './rules/no-implicit-any-catch'; +import { noImportOperatorsRule } from './rules/no-import-operators'; import { noIndexRule } from './rules/no-index'; import { noInternalRule } from './rules/no-internal'; import { noNestedSubscribeRule } from './rules/no-nested-subscribe'; @@ -70,6 +71,7 @@ const plugin = { 'no-ignored-subscription': noIgnoredSubscriptionRule, 'no-ignored-takewhile-value': noIgnoredTakewhileValueRule, 'no-implicit-any-catch': noImplicitAnyCatchRule, + 'no-import-operators': noImportOperatorsRule, 'no-index': noIndexRule, 'no-internal': noInternalRule, 'no-nested-subscribe': noNestedSubscribeRule, diff --git a/src/rules/no-import-operators.ts b/src/rules/no-import-operators.ts new file mode 100644 index 00000000..8c424a9d --- /dev/null +++ b/src/rules/no-import-operators.ts @@ -0,0 +1,45 @@ +import { TSESTree as es } from '@typescript-eslint/utils'; +import { ruleCreator } from '../utils'; + +export const noImportOperatorsRule = ruleCreator({ + defaultOptions: [], + meta: { + docs: { + description: 'Disallow importing operators from `rxjs/operators`.', + }, + messages: { + forbidden: 'RxJS imports from `rxjs/operators` are forbidden.', + }, + schema: [], + type: 'problem', + }, + name: 'no-import-operators', + create: (context) => { + return { + 'ImportDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { + context.report({ + messageId: 'forbidden', + node, + }); + }, + 'ImportExpression Literal[value="rxjs/operators"]': (node: es.Literal) => { + context.report({ + messageId: 'forbidden', + node, + }); + }, + 'ExportNamedDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { + context.report({ + messageId: 'forbidden', + node, + }); + }, + 'ExportAllDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { + context.report({ + messageId: 'forbidden', + node, + }); + }, + }; + }, +}); diff --git a/tests/rules/no-import-operators.test.ts b/tests/rules/no-import-operators.test.ts new file mode 100644 index 00000000..6352a901 --- /dev/null +++ b/tests/rules/no-import-operators.test.ts @@ -0,0 +1,82 @@ +import { stripIndent } from 'common-tags'; +import { noImportOperatorsRule } from '../../src/rules/no-import-operators'; +import { fromFixture } from '../etc'; +import { ruleTester } from '../rule-tester'; + +ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { + valid: [ + stripIndent` + // import declaration named + import { concat } from "rxjs"; + import { concat } from 'rxjs'; + `, + stripIndent` + // import declaration namespace + import * as Rx from "rxjs"; + `, + stripIndent` + // import expression + const { concat } = await import("rxjs"); + `, + stripIndent` + // import expression with identifier is not supported + const path = "rxjs/operators"; + const { concat } = await import(path); + `, + stripIndent` + // export named + export { concat } from "rxjs"; + `, + stripIndent` + // export all + export * from "rxjs"; + `, + stripIndent` + // unrelated import + import { ajax } from "rxjs/ajax"; + import { fromFetch } from "rxjs/fetch"; + import { TestScheduler } from "rxjs/testing"; + import { webSocket } from "rxjs/webSocket"; + import * as prefixedPackage from "rxjs-prefixed-package"; + `, + ], + invalid: [ + fromFixture( + stripIndent` + // import declaration named + import { concat } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + import { concat } from 'rxjs/operators'; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // import declaration namespace + import * as RxOperators from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // import expression + const { concat } = await import("rxjs/operators"); + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // export named + export { concat } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // export all + export * from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), + ], +}); From 7360e60f5ef143e8d191587959ac11c0adde33a6 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Mon, 18 Nov 2024 12:20:41 -0600 Subject: [PATCH 2/7] feat(no-import-operators): add suggestion We're only adding a suggestion right now because an auto-fixer might break if the operator is old and not exported from `rxjs` too. --- README.md | 2 +- docs/rules/no-import-operators.md | 2 + src/rules/no-import-operators.ts | 42 ++++++++++---- tests/rules/no-import-operators.test.ts | 76 +++++++++++++++++++++++-- 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ce22078a..592614bb 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ The package includes the following rules. | [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | | [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | | [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | | | | | +| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | | πŸ’‘ | | | | [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | | [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | | [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | diff --git a/docs/rules/no-import-operators.md b/docs/rules/no-import-operators.md index 3ae60792..2084af7e 100644 --- a/docs/rules/no-import-operators.md +++ b/docs/rules/no-import-operators.md @@ -1,5 +1,7 @@ # Disallow importing operators from `rxjs/operators` (`rxjs-x/no-import-operators`) +πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + This rule prevents importing from the `rxjs/operators` export site. diff --git a/src/rules/no-import-operators.ts b/src/rules/no-import-operators.ts index 8c424a9d..e200569d 100644 --- a/src/rules/no-import-operators.ts +++ b/src/rules/no-import-operators.ts @@ -7,38 +7,56 @@ export const noImportOperatorsRule = ruleCreator({ docs: { description: 'Disallow importing operators from `rxjs/operators`.', }, + hasSuggestions: true, messages: { forbidden: 'RxJS imports from `rxjs/operators` are forbidden.', + suggest: 'Import from `rxjs` instead.', }, schema: [], type: 'problem', }, name: 'no-import-operators', create: (context) => { - return { - 'ImportDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { + function getReplacement(rawLocation: string) { + const match = /^\s*('|")/.exec(rawLocation); + if (!match) { + return undefined; + } + const [, quote] = match; + if (/^['"]rxjs\/operators/.test(rawLocation)) { + return `${quote}rxjs${quote}`; + } + return undefined; + } + + function reportNode(node: es.Literal) { + const replacement = getReplacement(node.raw); + if (replacement) { context.report({ messageId: 'forbidden', node, + suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(node, replacement) }], }); - }, - 'ImportExpression Literal[value="rxjs/operators"]': (node: es.Literal) => { + } else { context.report({ messageId: 'forbidden', node, }); + } + } + + return { + 'ImportDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { + reportNode(node); + }, + 'ImportExpression Literal[value="rxjs/operators"]': (node: es.Literal) => { + reportNode(node); }, 'ExportNamedDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { - context.report({ - messageId: 'forbidden', - node, - }); + reportNode(node); }, 'ExportAllDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { - context.report({ - messageId: 'forbidden', - node, - }); + reportNode(node); }, }; }, diff --git a/tests/rules/no-import-operators.test.ts b/tests/rules/no-import-operators.test.ts index 6352a901..a7ea0594 100644 --- a/tests/rules/no-import-operators.test.ts +++ b/tests/rules/no-import-operators.test.ts @@ -45,38 +45,102 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { stripIndent` // import declaration named import { concat } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest 0] import { concat } from 'rxjs/operators'; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest 1] `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import declaration named + import { concat } from "rxjs"; + import { concat } from 'rxjs/operators'; + `, + }, + { + messageId: 'suggest', + output: stripIndent` + // import declaration named + import { concat } from "rxjs/operators"; + import { concat } from 'rxjs'; + `, + }, + ], + }, ), fromFixture( stripIndent` // import declaration namespace import * as RxOperators from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import declaration namespace + import * as RxOperators from "rxjs"; + `, + }, + ], + }, ), fromFixture( stripIndent` // import expression const { concat } = await import("rxjs/operators"); - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import expression + const { concat } = await import("rxjs"); + `, + }, + ], + }, ), fromFixture( stripIndent` // export named export { concat } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // export named + export { concat } from "rxjs"; + `, + }, + ], + }, ), fromFixture( stripIndent` // export all export * from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // export all + export * from "rxjs"; + `, + }, + ], + }, ), ], }); From f157a08c6aa7b64e42276c2fccd3a2c92abd1c96 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Tue, 19 Nov 2024 12:10:35 -0600 Subject: [PATCH 3/7] feat(no-import-operators): add fixer and suggestions It's ugly and needs refactoring, but the tests pass. --- README.md | 2 +- docs/rules/no-import-operators.md | 8 +- src/rules/no-import-operators.ts | 154 +++++++++++++++++++++--- tests/rules/no-import-operators.test.ts | 121 +++++++++++++++---- 4 files changed, 241 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 592614bb..5e27c63a 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ The package includes the following rules. | [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | | [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | | [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | | πŸ’‘ | | | +| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | πŸ”§ | πŸ’‘ | | | | [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | | [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | | [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | diff --git a/docs/rules/no-import-operators.md b/docs/rules/no-import-operators.md index 2084af7e..cf197ba7 100644 --- a/docs/rules/no-import-operators.md +++ b/docs/rules/no-import-operators.md @@ -1,14 +1,18 @@ # Disallow importing operators from `rxjs/operators` (`rxjs-x/no-import-operators`) -πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). +πŸ”§πŸ’‘ 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). This rule prevents importing from the `rxjs/operators` export site. Most operators were moved to the `rxjs` export site in RxJS v7.2.0 -(excepting a couple of old and deprecated operators). +(excepting a few old and deprecated operators). The `rxjs/operators` export site has since been deprecated and will be removed in a future major version. +Note that because a few operators were renamed or not migrated to the `rxjs` export site, +this rule may not provide an automatic fixer if renaming the import path is not guaranteed to be safe. +See the documentation linked below. + ## Rule details Examples of **incorrect** code for this rule: diff --git a/src/rules/no-import-operators.ts b/src/rules/no-import-operators.ts index e200569d..879d5630 100644 --- a/src/rules/no-import-operators.ts +++ b/src/rules/no-import-operators.ts @@ -1,12 +1,29 @@ -import { TSESTree as es } from '@typescript-eslint/utils'; +import { TSESTree as es, TSESLint } from '@typescript-eslint/utils'; +import { isIdentifier, isImportSpecifier, isLiteral } from '../etc'; import { ruleCreator } from '../utils'; +// See https://rxjs.dev/guide/importing#how-to-migrate + +const RENAMED_OPERATORS: Record = { + combineLatest: 'combineLatestWith', + concat: 'concatWith', + merge: 'mergeWith', + onErrorResumeNext: 'onErrorResumeNextWith', + race: 'raceWith', + zip: 'zipWith', +}; + +const DEPRECATED_OPERATORS = [ + 'partition', +]; + export const noImportOperatorsRule = ruleCreator({ defaultOptions: [], meta: { docs: { description: 'Disallow importing operators from `rxjs/operators`.', }, + fixable: 'code', hasSuggestions: true, messages: { forbidden: 'RxJS imports from `rxjs/operators` are forbidden.', @@ -17,46 +34,143 @@ export const noImportOperatorsRule = ruleCreator({ }, name: 'no-import-operators', create: (context) => { - function getReplacement(rawLocation: string) { - const match = /^\s*('|")/.exec(rawLocation); + function getQuote(raw: string): string | undefined { + const match = /^\s*('|")/.exec(raw); if (!match) { return undefined; } const [, quote] = match; + return quote; + } + + function getSourceReplacement(rawLocation: string): string | undefined { + const quote = getQuote(rawLocation); + if (!quote) { + return undefined; + } if (/^['"]rxjs\/operators/.test(rawLocation)) { return `${quote}rxjs${quote}`; } return undefined; } - function reportNode(node: es.Literal) { - const replacement = getReplacement(node.raw); - if (replacement) { - context.report({ - messageId: 'forbidden', - node, - suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(node, replacement) }], - }); + function getName(node: es.Identifier | es.StringLiteral): string { + return isIdentifier(node) ? node.name : node.value; + } + + function getSpecifierReplacement(name: string): string | undefined { + return RENAMED_OPERATORS[name]; + } + + function reportNode(source: es.Literal, importSpecifiers?: es.ImportSpecifier[], exportSpecifiers?: es.ExportSpecifier[]): void { + const replacement = getSourceReplacement(source.raw); + if ( + replacement + && !importSpecifiers?.some(s => DEPRECATED_OPERATORS.includes(getName(s.imported))) + && !exportSpecifiers?.some(s => DEPRECATED_OPERATORS.includes(getName(s.exported))) + ) { + if (importSpecifiers) { + function* fix(fixer: TSESLint.RuleFixer) { + // Rename the module name. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + yield fixer.replaceText(source, replacement!); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + for (const specifier of importSpecifiers!) { + const operatorName = getName(specifier.imported); + const specifierReplacement = getSpecifierReplacement(operatorName); + if (specifierReplacement) { + if (specifier.local.name === operatorName) { + // concat -> concatWith as concat + yield fixer.insertTextBefore(specifier.imported, specifierReplacement + ' as '); + } else if (isIdentifier(specifier.imported)) { + // concat as c -> concatWith as c + yield fixer.replaceText(specifier.imported, specifierReplacement); + } else { + // 'concat' as c -> 'concatWith' as c + const quote = getQuote(specifier.imported.raw); + if (!quote) { + continue; + } + yield fixer.replaceText(specifier.imported, quote + specifierReplacement + quote); + } + } + } + } + context.report({ + fix, + messageId: 'forbidden', + node: source, + suggest: [{ messageId: 'suggest', fix }], + }); + } else if (exportSpecifiers) { + function* fix(fixer: TSESLint.RuleFixer) { + // Rename the module name. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + yield fixer.replaceText(source, replacement!); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + for (const specifier of exportSpecifiers!) { + const operatorName = getName(specifier.local); + const specifierReplacement = getSpecifierReplacement(operatorName); + if (specifierReplacement) { + const exportedName = getName(specifier.exported); + if (exportedName === operatorName) { + // concat -> concatWith as concat + yield fixer.insertTextBefore(specifier.exported, specifierReplacement + ' as '); + } else if (isIdentifier(specifier.local)) { + // concat as c -> concatWith as c + yield fixer.replaceText(specifier.local, specifierReplacement); + } else { + // 'concat' as c -> 'concatWith' as c + const quote = getQuote(specifier.local.raw); + if (!quote) { + continue; + } + yield fixer.replaceText(specifier.local, quote + specifierReplacement + quote); + } + } + } + } + context.report({ + fix, + messageId: 'forbidden', + node: source, + suggest: [{ messageId: 'suggest', fix }], + }); + } else { + context.report({ + messageId: 'forbidden', + node: source, + suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(source, replacement) }], + }); + } } else { context.report({ messageId: 'forbidden', - node, + node: source, }); } } return { - 'ImportDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { - reportNode(node); + 'ImportDeclaration[source.value="rxjs/operators"]': (node: es.ImportDeclaration) => { + // Exclude side effect imports, default imports, and namespace imports. + const specifiers = node.specifiers.length && node.specifiers.every(s => isImportSpecifier(s)) + ? node.specifiers + : undefined; + reportNode(node.source, specifiers); }, - 'ImportExpression Literal[value="rxjs/operators"]': (node: es.Literal) => { - reportNode(node); + 'ImportExpression[source.value="rxjs/operators"]': (node: es.ImportExpression) => { + if (isLiteral(node.source)) { + reportNode(node.source); + } }, - 'ExportNamedDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { - reportNode(node); + 'ExportNamedDeclaration[source.value="rxjs/operators"]': (node: es.ExportNamedDeclarationWithSource) => { + reportNode(node.source, undefined, node.specifiers); }, - 'ExportAllDeclaration Literal[value="rxjs/operators"]': (node: es.Literal) => { - reportNode(node); + 'ExportAllDeclaration[source.value="rxjs/operators"]': (node: es.ExportAllDeclaration) => { + reportNode(node.source); }, }; }, diff --git a/tests/rules/no-import-operators.test.ts b/tests/rules/no-import-operators.test.ts index a7ea0594..bd5eaed5 100644 --- a/tests/rules/no-import-operators.test.ts +++ b/tests/rules/no-import-operators.test.ts @@ -7,8 +7,8 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { valid: [ stripIndent` // import declaration named - import { concat } from "rxjs"; - import { concat } from 'rxjs'; + import { concatWith } from "rxjs"; + import { mergeWith } from 'rxjs'; `, stripIndent` // import declaration namespace @@ -16,16 +16,16 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { `, stripIndent` // import expression - const { concat } = await import("rxjs"); + const { concatWith } = await import("rxjs"); `, stripIndent` - // import expression with identifier is not supported + // import expression without a string literal is not supported const path = "rxjs/operators"; const { concat } = await import(path); `, stripIndent` // export named - export { concat } from "rxjs"; + export { concatWith, mergeWith as m } from "rxjs"; `, stripIndent` // export all @@ -44,32 +44,44 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { fromFixture( stripIndent` // import declaration named - import { concat } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest 0] - import { concat } from 'rxjs/operators'; - ~~~~~~~~~~~~~~~~ [forbidden suggest 1] + import { concat, map } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden suggest 0] + import { 'merge' as m, race as r } from 'rxjs/operators'; + ~~~~~~~~~~~~~~~~ [forbidden suggest 1] `, { + output: stripIndent` + // import declaration named + import { concatWith as concat, map } from "rxjs"; + import { 'mergeWith' as m, raceWith as r } from 'rxjs'; + `, suggestions: [ { messageId: 'suggest', output: stripIndent` // import declaration named - import { concat } from "rxjs"; - import { concat } from 'rxjs/operators'; + import { concatWith as concat, map } from "rxjs"; + import { 'merge' as m, race as r } from 'rxjs/operators'; `, }, { messageId: 'suggest', output: stripIndent` // import declaration named - import { concat } from "rxjs/operators"; - import { concat } from 'rxjs'; + import { concat, map } from "rxjs/operators"; + import { 'mergeWith' as m, raceWith as r } from 'rxjs'; `, }, ], }, ), + fromFixture( + stripIndent` + // import declaration named, deprecated operator + import { partition } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), fromFixture( stripIndent` // import declaration namespace @@ -88,20 +100,76 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { ], }, ), + fromFixture( + stripIndent` + // import declaration default + import RxOperators, { map } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden suggest] + `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import declaration default + import RxOperators, { map } from "rxjs"; + `, + }, + ], + }, + ), fromFixture( stripIndent` // import expression - const { concat } = await import("rxjs/operators"); - ~~~~~~~~~~~~~~~~ [forbidden suggest] + const { concat, merge: m, map } = await import("rxjs/operators"); + ~~~~~~~~~~~~~~~~ [forbidden suggest] + `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import expression + const { concat, merge: m, map } = await import("rxjs"); + `, + }, + ], + }, + ), + fromFixture( + stripIndent` + // import expression, separated import + const opPromise = import("rxjs/operators"); + ~~~~~~~~~~~~~~~~ [forbidden suggest] + const { concat } = await opPromise; + `, + { + suggestions: [ + { + messageId: 'suggest', + output: stripIndent` + // import expression, separated import + const opPromise = import("rxjs"); + const { concat } = await opPromise; + `, + }, + ], + }, + ), + fromFixture( + stripIndent` + // import expression, deprecated operator + const { concat, partition } = await import("rxjs/operators"); + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, { suggestions: [ { messageId: 'suggest', output: stripIndent` - // import expression - const { concat } = await import("rxjs"); - `, + // import expression, deprecated operator + const { concat, partition } = await import("rxjs"); + `, }, ], }, @@ -109,21 +177,32 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { fromFixture( stripIndent` // export named - export { concat } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest] + export { concat, merge as m, map, 'race' as "r" } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, { + output: stripIndent` + // export named + export { concatWith as concat, mergeWith as m, map, 'raceWith' as "r" } from "rxjs"; + `, suggestions: [ { messageId: 'suggest', output: stripIndent` // export named - export { concat } from "rxjs"; + export { concatWith as concat, mergeWith as m, map, 'raceWith' as "r" } from "rxjs"; `, }, ], }, ), + fromFixture( + stripIndent` + // export named, deprecated operator + export { concat, partition } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden] + `, + ), fromFixture( stripIndent` // export all From 7f6c2cbb45ee262e1c5bef29f895f318ce7a919f Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Tue, 19 Nov 2024 14:29:01 -0600 Subject: [PATCH 4/7] fix(no-import-operators): fix alias===operator bug - When alias name and operator name equal, we also need to check range to determine if the alias exists or not. - Refactored rule for less duplication. --- src/rules/no-import-operators.ts | 155 +++++++++++------------- tests/rules/no-import-operators.test.ts | 32 +++-- 2 files changed, 93 insertions(+), 94 deletions(-) diff --git a/src/rules/no-import-operators.ts b/src/rules/no-import-operators.ts index 879d5630..62bb7486 100644 --- a/src/rules/no-import-operators.ts +++ b/src/rules/no-import-operators.ts @@ -54,111 +54,100 @@ export const noImportOperatorsRule = ruleCreator({ return undefined; } + function hasDeprecatedOperators(specifiers?: es.ImportSpecifier[] | es.ExportSpecifier[]): boolean { + return !!specifiers?.some(s => DEPRECATED_OPERATORS.includes(getName(getOperatorNode(s)))); + } + function getName(node: es.Identifier | es.StringLiteral): string { return isIdentifier(node) ? node.name : node.value; } - function getSpecifierReplacement(name: string): string | undefined { + function getOperatorNode(node: es.ImportSpecifier | es.ExportSpecifier): es.Identifier | es.StringLiteral { + return isImportSpecifier(node) ? node.imported : node.local; + } + + function getAliasNode(node: es.ImportSpecifier | es.ExportSpecifier): es.Identifier | es.StringLiteral { + return isImportSpecifier(node) ? node.local : node.exported; + } + + function getOperatorReplacement(name: string): string | undefined { return RENAMED_OPERATORS[name]; } - function reportNode(source: es.Literal, importSpecifiers?: es.ImportSpecifier[], exportSpecifiers?: es.ExportSpecifier[]): void { - const replacement = getSourceReplacement(source.raw); - if ( - replacement - && !importSpecifiers?.some(s => DEPRECATED_OPERATORS.includes(getName(s.imported))) - && !exportSpecifiers?.some(s => DEPRECATED_OPERATORS.includes(getName(s.exported))) - ) { - if (importSpecifiers) { - function* fix(fixer: TSESLint.RuleFixer) { - // Rename the module name. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - yield fixer.replaceText(source, replacement!); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - for (const specifier of importSpecifiers!) { - const operatorName = getName(specifier.imported); - const specifierReplacement = getSpecifierReplacement(operatorName); - if (specifierReplacement) { - if (specifier.local.name === operatorName) { - // concat -> concatWith as concat - yield fixer.insertTextBefore(specifier.imported, specifierReplacement + ' as '); - } else if (isIdentifier(specifier.imported)) { - // concat as c -> concatWith as c - yield fixer.replaceText(specifier.imported, specifierReplacement); - } else { - // 'concat' as c -> 'concatWith' as c - const quote = getQuote(specifier.imported.raw); - if (!quote) { - continue; - } - yield fixer.replaceText(specifier.imported, quote + specifierReplacement + quote); - } - } - } + function isNodesEqual(a: es.Node, b: es.Node): boolean { + return a.range[0] === b.range[0] && a.range[1] === b.range[1]; + } + + function createFix(source: es.Node, replacement: string, specifiers: es.ImportSpecifier[] | es.ExportSpecifier[]) { + return function* fix(fixer: TSESLint.RuleFixer) { + // Rename the module name. + yield fixer.replaceText(source, replacement); + + // Rename the imported operators if necessary. + for (const specifier of specifiers) { + const operatorNode = getOperatorNode(specifier); + const operatorName = getName(operatorNode); + + const operatorReplacement = getOperatorReplacement(operatorName); + if (!operatorReplacement) { + // The operator has the same name. + continue; } - context.report({ - fix, - messageId: 'forbidden', - node: source, - suggest: [{ messageId: 'suggest', fix }], - }); - } else if (exportSpecifiers) { - function* fix(fixer: TSESLint.RuleFixer) { - // Rename the module name. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - yield fixer.replaceText(source, replacement!); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - for (const specifier of exportSpecifiers!) { - const operatorName = getName(specifier.local); - const specifierReplacement = getSpecifierReplacement(operatorName); - if (specifierReplacement) { - const exportedName = getName(specifier.exported); - if (exportedName === operatorName) { - // concat -> concatWith as concat - yield fixer.insertTextBefore(specifier.exported, specifierReplacement + ' as '); - } else if (isIdentifier(specifier.local)) { - // concat as c -> concatWith as c - yield fixer.replaceText(specifier.local, specifierReplacement); - } else { - // 'concat' as c -> 'concatWith' as c - const quote = getQuote(specifier.local.raw); - if (!quote) { - continue; - } - yield fixer.replaceText(specifier.local, quote + specifierReplacement + quote); - } - } + + const aliasNode = getAliasNode(specifier); + if (isNodesEqual(aliasNode, operatorNode)) { + // concat -> concatWith as concat + yield fixer.insertTextBefore(operatorNode, operatorReplacement + ' as '); + } else if (isIdentifier(operatorNode)) { + // concat as c -> concatWith as c + yield fixer.replaceText(operatorNode, operatorReplacement); + } else { + // 'concat' as c -> 'concatWith' as c + const quote = getQuote(operatorNode.raw); + if (!quote) { + continue; } + yield fixer.replaceText(operatorNode, quote + operatorReplacement + quote); } - context.report({ - fix, - messageId: 'forbidden', - node: source, - suggest: [{ messageId: 'suggest', fix }], - }); - } else { - context.report({ - messageId: 'forbidden', - node: source, - suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(source, replacement) }], - }); } - } else { + }; + } + + function reportNode(source: es.Literal, specifiers?: es.ImportSpecifier[] | es.ExportSpecifier[]): void { + const replacement = getSourceReplacement(source.raw); + if (!replacement || hasDeprecatedOperators(specifiers)) { + context.report({ + messageId: 'forbidden', + node: source, + }); + return; + } + + if (!specifiers) { context.report({ messageId: 'forbidden', node: source, + suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(source, replacement) }], }); + return; } + + const fix = createFix(source, replacement, specifiers); + context.report({ + fix, + messageId: 'forbidden', + node: source, + suggest: [{ messageId: 'suggest', fix }], + }); } return { 'ImportDeclaration[source.value="rxjs/operators"]': (node: es.ImportDeclaration) => { // Exclude side effect imports, default imports, and namespace imports. - const specifiers = node.specifiers.length && node.specifiers.every(s => isImportSpecifier(s)) + const specifiers = node.specifiers.length && node.specifiers.every(importClause => isImportSpecifier(importClause)) ? node.specifiers : undefined; + reportNode(node.source, specifiers); }, 'ImportExpression[source.value="rxjs/operators"]': (node: es.ImportExpression) => { @@ -167,7 +156,7 @@ export const noImportOperatorsRule = ruleCreator({ } }, 'ExportNamedDeclaration[source.value="rxjs/operators"]': (node: es.ExportNamedDeclarationWithSource) => { - reportNode(node.source, undefined, node.specifiers); + reportNode(node.source, node.specifiers); }, 'ExportAllDeclaration[source.value="rxjs/operators"]': (node: es.ExportAllDeclaration) => { reportNode(node.source); diff --git a/tests/rules/no-import-operators.test.ts b/tests/rules/no-import-operators.test.ts index bd5eaed5..50fa5508 100644 --- a/tests/rules/no-import-operators.test.ts +++ b/tests/rules/no-import-operators.test.ts @@ -44,32 +44,42 @@ ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { fromFixture( stripIndent` // import declaration named - import { concat, map } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest 0] - import { 'merge' as m, race as r } from 'rxjs/operators'; - ~~~~~~~~~~~~~~~~ [forbidden suggest 1] + import { map as m, filter, 'tap' as tap } from "rxjs/operators"; + ~~~~~~~~~~~~~~~~ [forbidden suggest] `, { output: stripIndent` // import declaration named - import { concatWith as concat, map } from "rxjs"; - import { 'mergeWith' as m, raceWith as r } from 'rxjs'; + import { map as m, filter, 'tap' as tap } from "rxjs"; `, suggestions: [ { messageId: 'suggest', output: stripIndent` // import declaration named - import { concatWith as concat, map } from "rxjs"; - import { 'merge' as m, race as r } from 'rxjs/operators'; + import { map as m, filter, 'tap' as tap } from "rxjs"; `, }, + ], + }, + ), + fromFixture( + stripIndent` + // import declaration named, renamed operators + import { 'merge' as m, race as race } from 'rxjs/operators'; + ~~~~~~~~~~~~~~~~ [forbidden suggest] + `, + { + output: stripIndent` + // import declaration named, renamed operators + import { 'mergeWith' as m, raceWith as race } from 'rxjs'; + `, + suggestions: [ { messageId: 'suggest', output: stripIndent` - // import declaration named - import { concat, map } from "rxjs/operators"; - import { 'mergeWith' as m, raceWith as r } from 'rxjs'; + // import declaration named, renamed operators + import { 'mergeWith' as m, raceWith as race } from 'rxjs'; `, }, ], From 94b51b3000f41bd60c565fc6a378445ac69d77eb Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Tue, 19 Nov 2024 14:33:14 -0600 Subject: [PATCH 5/7] refactor(prefer-import-root-operators): rename rule --- README.md | 90 +++++++++---------- ...ors.md => prefer-import-root-operators.md} | 2 +- src/index.ts | 4 +- ...ors.ts => prefer-import-root-operators.ts} | 4 +- ...s => prefer-import-root-operators.test.ts} | 4 +- 5 files changed, 52 insertions(+), 52 deletions(-) rename docs/rules/{no-import-operators.md => prefer-import-root-operators.md} (92%) rename src/rules/{no-import-operators.ts => prefer-import-root-operators.ts} (98%) rename tests/rules/{no-import-operators.test.ts => prefer-import-root-operators.test.ts} (97%) diff --git a/README.md b/README.md index 5e27c63a..e0762e64 100644 --- a/README.md +++ b/README.md @@ -64,50 +64,50 @@ The package includes the following rules. πŸ’­ Requires [type information](https://typescript-eslint.io/linting/typed-linting).\ ❌ Deprecated. -| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | -| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | -| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | -| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | -| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | -| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | -| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | -| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | -| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | ❌ | -| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | -| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | -| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | -| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | -| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | -| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | -| [no-ignored-default-value](docs/rules/no-ignored-default-value.md) | Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value. | | | | πŸ’­ | | -| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | -| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | -| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | -| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | -| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | -| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | -| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | -| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [no-import-operators](docs/rules/no-import-operators.md) | Disallow importing operators from `rxjs/operators`. | | πŸ”§ | πŸ’‘ | | | -| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | -| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | -| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | -| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | -| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | -| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | -| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | -| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | -| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | -| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | -| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | πŸ’‘ | πŸ’­ | | -| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | -| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | -| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | βœ… | | | πŸ’­ | | -| [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | πŸ’­ | | -| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | πŸ’­ | | +| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | +| :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | +| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | +| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | +| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | +| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | +| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | +| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | +| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | ❌ | +| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | +| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | +| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | +| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | +| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | +| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | +| [no-ignored-default-value](docs/rules/no-ignored-default-value.md) | Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value. | | | | πŸ’­ | | +| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | +| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | +| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | +| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | +| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | +| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | +| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | +| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | +| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | +| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | +| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | +| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | +| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | +| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | +| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | +| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | +| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | +| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | πŸ’‘ | πŸ’­ | | +| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | +| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | +| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | βœ… | | | πŸ’­ | | +| [prefer-import-root-operators](docs/rules/prefer-import-root-operators.md) | Disallow importing operators from `rxjs/operators`. | | πŸ”§ | πŸ’‘ | | | +| [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | πŸ’­ | | +| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | πŸ’­ | | diff --git a/docs/rules/no-import-operators.md b/docs/rules/prefer-import-root-operators.md similarity index 92% rename from docs/rules/no-import-operators.md rename to docs/rules/prefer-import-root-operators.md index cf197ba7..b6b8db14 100644 --- a/docs/rules/no-import-operators.md +++ b/docs/rules/prefer-import-root-operators.md @@ -1,4 +1,4 @@ -# Disallow importing operators from `rxjs/operators` (`rxjs-x/no-import-operators`) +# Disallow importing operators from `rxjs/operators` (`rxjs-x/prefer-import-root-operators`) πŸ”§πŸ’‘ 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). diff --git a/src/index.ts b/src/index.ts index c20524de..5453f89f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import { noIgnoredSubscribeRule } from './rules/no-ignored-subscribe'; import { noIgnoredSubscriptionRule } from './rules/no-ignored-subscription'; import { noIgnoredTakewhileValueRule } from './rules/no-ignored-takewhile-value'; import { noImplicitAnyCatchRule } from './rules/no-implicit-any-catch'; -import { noImportOperatorsRule } from './rules/no-import-operators'; +import { preferImportRootOperatorsRule } from './rules/prefer-import-root-operators'; import { noIndexRule } from './rules/no-index'; import { noInternalRule } from './rules/no-internal'; import { noNestedSubscribeRule } from './rules/no-nested-subscribe'; @@ -71,7 +71,7 @@ const plugin = { 'no-ignored-subscription': noIgnoredSubscriptionRule, 'no-ignored-takewhile-value': noIgnoredTakewhileValueRule, 'no-implicit-any-catch': noImplicitAnyCatchRule, - 'no-import-operators': noImportOperatorsRule, + 'prefer-import-root-operators': preferImportRootOperatorsRule, 'no-index': noIndexRule, 'no-internal': noInternalRule, 'no-nested-subscribe': noNestedSubscribeRule, diff --git a/src/rules/no-import-operators.ts b/src/rules/prefer-import-root-operators.ts similarity index 98% rename from src/rules/no-import-operators.ts rename to src/rules/prefer-import-root-operators.ts index 62bb7486..57098211 100644 --- a/src/rules/no-import-operators.ts +++ b/src/rules/prefer-import-root-operators.ts @@ -17,7 +17,7 @@ const DEPRECATED_OPERATORS = [ 'partition', ]; -export const noImportOperatorsRule = ruleCreator({ +export const preferImportRootOperatorsRule = ruleCreator({ defaultOptions: [], meta: { docs: { @@ -32,7 +32,7 @@ export const noImportOperatorsRule = ruleCreator({ schema: [], type: 'problem', }, - name: 'no-import-operators', + name: 'prefer-import-root-operators', create: (context) => { function getQuote(raw: string): string | undefined { const match = /^\s*('|")/.exec(raw); diff --git a/tests/rules/no-import-operators.test.ts b/tests/rules/prefer-import-root-operators.test.ts similarity index 97% rename from tests/rules/no-import-operators.test.ts rename to tests/rules/prefer-import-root-operators.test.ts index 50fa5508..63b9d9e1 100644 --- a/tests/rules/no-import-operators.test.ts +++ b/tests/rules/prefer-import-root-operators.test.ts @@ -1,9 +1,9 @@ import { stripIndent } from 'common-tags'; -import { noImportOperatorsRule } from '../../src/rules/no-import-operators'; +import { preferImportRootOperatorsRule } from '../../src/rules/prefer-import-root-operators'; import { fromFixture } from '../etc'; import { ruleTester } from '../rule-tester'; -ruleTester({ types: false }).run('no-import-operators', noImportOperatorsRule, { +ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRootOperatorsRule, { valid: [ stripIndent` // import declaration named From cedc593645d03f6182c3faf47b5254560930bdf6 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Tue, 19 Nov 2024 14:50:04 -0600 Subject: [PATCH 6/7] feat(prefer-import-root-operators): separate message when no fixer provided Warn about possible renames or partition being gone. --- src/rules/prefer-import-root-operators.ts | 11 ++++++----- tests/rules/prefer-import-root-operators.test.ts | 16 ++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/rules/prefer-import-root-operators.ts b/src/rules/prefer-import-root-operators.ts index 57098211..775d067d 100644 --- a/src/rules/prefer-import-root-operators.ts +++ b/src/rules/prefer-import-root-operators.ts @@ -26,11 +26,12 @@ export const preferImportRootOperatorsRule = ruleCreator({ fixable: 'code', hasSuggestions: true, messages: { - forbidden: 'RxJS imports from `rxjs/operators` are forbidden.', - suggest: 'Import from `rxjs` instead.', + forbidden: 'RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead.', + forbiddenWithoutFix: 'RxJS imports from `rxjs/operators` are forbidden; import from `rxjs` instead. Note some operators may have been renamed or deprecated.', + suggest: 'Replace with import from `rxjs`.', }, schema: [], - type: 'problem', + type: 'suggestion', }, name: 'prefer-import-root-operators', create: (context) => { @@ -117,7 +118,7 @@ export const preferImportRootOperatorsRule = ruleCreator({ const replacement = getSourceReplacement(source.raw); if (!replacement || hasDeprecatedOperators(specifiers)) { context.report({ - messageId: 'forbidden', + messageId: 'forbiddenWithoutFix', node: source, }); return; @@ -125,7 +126,7 @@ export const preferImportRootOperatorsRule = ruleCreator({ if (!specifiers) { context.report({ - messageId: 'forbidden', + messageId: 'forbiddenWithoutFix', node: source, suggest: [{ messageId: 'suggest', fix: (fixer) => fixer.replaceText(source, replacement) }], }); diff --git a/tests/rules/prefer-import-root-operators.test.ts b/tests/rules/prefer-import-root-operators.test.ts index 63b9d9e1..e7d2acc0 100644 --- a/tests/rules/prefer-import-root-operators.test.ts +++ b/tests/rules/prefer-import-root-operators.test.ts @@ -89,14 +89,14 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // import declaration named, deprecated operator import { partition } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix] `, ), fromFixture( stripIndent` // import declaration namespace import * as RxOperators from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] `, { suggestions: [ @@ -114,7 +114,7 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // import declaration default import RxOperators, { map } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] `, { suggestions: [ @@ -132,7 +132,7 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // import expression const { concat, merge: m, map } = await import("rxjs/operators"); - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] `, { suggestions: [ @@ -150,7 +150,7 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // import expression, separated import const opPromise = import("rxjs/operators"); - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] const { concat } = await opPromise; `, { @@ -170,7 +170,7 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // import expression, deprecated operator const { concat, partition } = await import("rxjs/operators"); - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] `, { suggestions: [ @@ -210,14 +210,14 @@ ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRoo stripIndent` // export named, deprecated operator export { concat, partition } from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix] `, ), fromFixture( stripIndent` // export all export * from "rxjs/operators"; - ~~~~~~~~~~~~~~~~ [forbidden suggest] + ~~~~~~~~~~~~~~~~ [forbiddenWithoutFix suggest] `, { suggestions: [ From fca768c60eb9d634a6f3b139ae3dd549946dcfff Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Thu, 21 Nov 2024 11:55:02 -0600 Subject: [PATCH 7/7] refactor(prefer-root-operators): rename rule Import isn't accurate because the rule also covers exports too. --- README.md | 90 +++++++++---------- ...-operators.md => prefer-root-operators.md} | 2 +- src/index.ts | 4 +- ...-operators.ts => prefer-root-operators.ts} | 4 +- ....test.ts => prefer-root-operators.test.ts} | 4 +- 5 files changed, 52 insertions(+), 52 deletions(-) rename docs/rules/{prefer-import-root-operators.md => prefer-root-operators.md} (97%) rename src/rules/{prefer-import-root-operators.ts => prefer-root-operators.ts} (98%) rename tests/rules/{prefer-import-root-operators.test.ts => prefer-root-operators.test.ts} (97%) diff --git a/README.md b/README.md index e0762e64..b717b793 100644 --- a/README.md +++ b/README.md @@ -64,50 +64,50 @@ The package includes the following rules. πŸ’­ Requires [type information](https://typescript-eslint.io/linting/typed-linting).\ ❌ Deprecated. -| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | -| :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | -| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | -| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | -| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | -| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | -| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | -| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | -| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | ❌ | -| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | -| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | -| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | -| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | -| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | -| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | -| [no-ignored-default-value](docs/rules/no-ignored-default-value.md) | Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value. | | | | πŸ’­ | | -| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | -| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | -| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | -| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | -| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | -| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | -| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | -| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | -| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | -| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | -| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | -| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | -| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | -| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | -| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | -| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | -| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | -| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | πŸ’‘ | πŸ’­ | | -| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | -| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | -| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | βœ… | | | πŸ’­ | | -| [prefer-import-root-operators](docs/rules/prefer-import-root-operators.md) | Disallow importing operators from `rxjs/operators`. | | πŸ”§ | πŸ’‘ | | | -| [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | πŸ’­ | | -| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | πŸ’­ | | +| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | +| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | +| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | +| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | +| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | +| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | +| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | +| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | +| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | ❌ | +| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | +| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | +| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | +| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | +| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | +| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | +| [no-ignored-default-value](docs/rules/no-ignored-default-value.md) | Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value. | | | | πŸ’­ | | +| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | +| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | +| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | +| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | +| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | +| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | +| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | +| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | +| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | +| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | +| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | +| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | +| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | +| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | +| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | +| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | +| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | +| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | πŸ’‘ | πŸ’­ | | +| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | +| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | +| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | βœ… | | | πŸ’­ | | +| [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [prefer-root-operators](docs/rules/prefer-root-operators.md) | Disallow importing operators from `rxjs/operators`. | | πŸ”§ | πŸ’‘ | | | +| [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | πŸ’­ | | +| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | πŸ’­ | | diff --git a/docs/rules/prefer-import-root-operators.md b/docs/rules/prefer-root-operators.md similarity index 97% rename from docs/rules/prefer-import-root-operators.md rename to docs/rules/prefer-root-operators.md index b6b8db14..bfc205c6 100644 --- a/docs/rules/prefer-import-root-operators.md +++ b/docs/rules/prefer-root-operators.md @@ -1,4 +1,4 @@ -# Disallow importing operators from `rxjs/operators` (`rxjs-x/prefer-import-root-operators`) +# Disallow importing operators from `rxjs/operators` (`rxjs-x/prefer-root-operators`) πŸ”§πŸ’‘ 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). diff --git a/src/index.ts b/src/index.ts index 5453f89f..ab3491b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,6 @@ import { noIgnoredSubscribeRule } from './rules/no-ignored-subscribe'; import { noIgnoredSubscriptionRule } from './rules/no-ignored-subscription'; import { noIgnoredTakewhileValueRule } from './rules/no-ignored-takewhile-value'; import { noImplicitAnyCatchRule } from './rules/no-implicit-any-catch'; -import { preferImportRootOperatorsRule } from './rules/prefer-import-root-operators'; import { noIndexRule } from './rules/no-index'; import { noInternalRule } from './rules/no-internal'; import { noNestedSubscribeRule } from './rules/no-nested-subscribe'; @@ -43,6 +42,7 @@ import { noUnsafeSubjectNext } from './rules/no-unsafe-subject-next'; import { noUnsafeSwitchmapRule } from './rules/no-unsafe-switchmap'; import { noUnsafeTakeuntilRule } from './rules/no-unsafe-takeuntil'; import { preferObserverRule } from './rules/prefer-observer'; +import { preferRootOperatorsRule } from './rules/prefer-root-operators'; import { suffixSubjectsRule } from './rules/suffix-subjects'; import { throwErrorRule } from './rules/throw-error'; @@ -71,7 +71,6 @@ const plugin = { 'no-ignored-subscription': noIgnoredSubscriptionRule, 'no-ignored-takewhile-value': noIgnoredTakewhileValueRule, 'no-implicit-any-catch': noImplicitAnyCatchRule, - 'prefer-import-root-operators': preferImportRootOperatorsRule, 'no-index': noIndexRule, 'no-internal': noInternalRule, 'no-nested-subscribe': noNestedSubscribeRule, @@ -90,6 +89,7 @@ const plugin = { 'no-unsafe-switchmap': noUnsafeSwitchmapRule, 'no-unsafe-takeuntil': noUnsafeTakeuntilRule, 'prefer-observer': preferObserverRule, + 'prefer-root-operators': preferRootOperatorsRule, 'suffix-subjects': suffixSubjectsRule, 'throw-error': throwErrorRule, }, diff --git a/src/rules/prefer-import-root-operators.ts b/src/rules/prefer-root-operators.ts similarity index 98% rename from src/rules/prefer-import-root-operators.ts rename to src/rules/prefer-root-operators.ts index 775d067d..1e7e793b 100644 --- a/src/rules/prefer-import-root-operators.ts +++ b/src/rules/prefer-root-operators.ts @@ -17,7 +17,7 @@ const DEPRECATED_OPERATORS = [ 'partition', ]; -export const preferImportRootOperatorsRule = ruleCreator({ +export const preferRootOperatorsRule = ruleCreator({ defaultOptions: [], meta: { docs: { @@ -33,7 +33,7 @@ export const preferImportRootOperatorsRule = ruleCreator({ schema: [], type: 'suggestion', }, - name: 'prefer-import-root-operators', + name: 'prefer-root-operators', create: (context) => { function getQuote(raw: string): string | undefined { const match = /^\s*('|")/.exec(raw); diff --git a/tests/rules/prefer-import-root-operators.test.ts b/tests/rules/prefer-root-operators.test.ts similarity index 97% rename from tests/rules/prefer-import-root-operators.test.ts rename to tests/rules/prefer-root-operators.test.ts index e7d2acc0..4627b494 100644 --- a/tests/rules/prefer-import-root-operators.test.ts +++ b/tests/rules/prefer-root-operators.test.ts @@ -1,9 +1,9 @@ import { stripIndent } from 'common-tags'; -import { preferImportRootOperatorsRule } from '../../src/rules/prefer-import-root-operators'; +import { preferRootOperatorsRule } from '../../src/rules/prefer-root-operators'; import { fromFixture } from '../etc'; import { ruleTester } from '../rule-tester'; -ruleTester({ types: false }).run('prefer-import-root-operators', preferImportRootOperatorsRule, { +ruleTester({ types: false }).run('prefer-root-operators', preferRootOperatorsRule, { valid: [ stripIndent` // import declaration named