From 56abc56faeabfcad99a5fc585cc0b023c1b04681 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 27 Oct 2022 17:59:48 +0200 Subject: [PATCH] feat(eslint): remove rules using @Effect --- .../no-effect-decorator-and-creator.spec.ts | 103 --------- .../spec/rules/no-effect-decorator.spec.ts | 214 ------------------ .../configs/all-requiring-type-checking.ts | 2 - modules/eslint-plugin/src/configs/all.ts | 2 - .../effects-requiring-type-checking.ts | 2 - .../effects-strict-requiring-type-checking.ts | 2 - .../src/configs/effects-strict.ts | 2 - modules/eslint-plugin/src/configs/effects.ts | 2 - .../recommended-requiring-type-checking.ts | 2 - .../eslint-plugin/src/configs/recommended.ts | 2 - .../configs/strict-requiring-type-checking.ts | 2 - modules/eslint-plugin/src/configs/strict.ts | 2 - .../no-effect-decorator-and-creator.ts | 111 --------- .../src/rules/effects/no-effect-decorator.ts | 123 ---------- .../content/guide/eslint-plugin/index.md | 2 - .../rules/no-effect-decorator-and-creator.md | 49 ---- .../rules/no-effect-decorator.md | 48 ---- 17 files changed, 670 deletions(-) delete mode 100644 modules/eslint-plugin/spec/rules/no-effect-decorator-and-creator.spec.ts delete mode 100644 modules/eslint-plugin/spec/rules/no-effect-decorator.spec.ts delete mode 100644 modules/eslint-plugin/src/rules/effects/no-effect-decorator-and-creator.ts delete mode 100644 modules/eslint-plugin/src/rules/effects/no-effect-decorator.ts delete mode 100644 projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator-and-creator.md delete mode 100644 projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator.md diff --git a/modules/eslint-plugin/spec/rules/no-effect-decorator-and-creator.spec.ts b/modules/eslint-plugin/spec/rules/no-effect-decorator-and-creator.spec.ts deleted file mode 100644 index 9b1be646d9..0000000000 --- a/modules/eslint-plugin/spec/rules/no-effect-decorator-and-creator.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { - ESLintUtils, - TSESLint, -} from '@typescript-eslint/experimental-utils'; -import { fromFixture } from 'eslint-etc'; -import * as path from 'path'; -import rule, { - noEffectDecoratorAndCreator, - noEffectDecoratorAndCreatorSuggest, -} from '../../src/rules/effects/no-effect-decorator-and-creator'; -import { ruleTester } from '../utils'; - -type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule; -type Options = ESLintUtils.InferOptionsTypeFromRule; -type RunTests = TSESLint.RunTests; - -const valid: () => RunTests['valid'] = () => [ - ` -@Injectable() -export class FixtureEffects { - creator = createEffect(() => this.actions) - constructor(private actions: Actions) {} -}`, - ` -@Injectable() -export class FixtureEffects { - @Effect({ dispatch: false }) - decorator = this.actions - constructor(private actions: Actions) {} -}`, -]; - -const invalid: () => RunTests['invalid'] = () => [ - fromFixture( - ` -import { Effect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect() - both = createEffect(() => this.actions) - ~~~~ [${noEffectDecoratorAndCreator}] - constructor(private actions: Actions) {} -} -@Injectable() -export class FixtureEffects2 { - @Effect() source$ = defer(() => { - return mySocketService.connect() - }) -}`, - { - output: ` -import { Effect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - - both = createEffect(() => this.actions) - constructor(private actions: Actions) {} -} -@Injectable() -export class FixtureEffects2 { - @Effect() source$ = defer(() => { - return mySocketService.connect() - }) -}`, - } - ), - { - code: ` -import {Effect} from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect({ dispatch: false }) - both = createEffect(() => this.actions) - constructor(private actions: Actions) {} -}`, - errors: [ - { - column: 3, - endColumn: 7, - line: 6, - messageId: noEffectDecoratorAndCreator, - suggestions: [ - { - messageId: noEffectDecoratorAndCreatorSuggest as MessageIds, - output: ` - -@Injectable() -export class FixtureEffects { - - both = createEffect(() => this.actions) - constructor(private actions: Actions) {} -}`, - }, - ], - }, - ], - }, -]; - -ruleTester().run(path.parse(__filename).name, rule, { - valid: valid(), - invalid: invalid(), -}); diff --git a/modules/eslint-plugin/spec/rules/no-effect-decorator.spec.ts b/modules/eslint-plugin/spec/rules/no-effect-decorator.spec.ts deleted file mode 100644 index 81122d8f9b..0000000000 --- a/modules/eslint-plugin/spec/rules/no-effect-decorator.spec.ts +++ /dev/null @@ -1,214 +0,0 @@ -import type { - ESLintUtils, - TSESLint, -} from '@typescript-eslint/experimental-utils'; -import { fromFixture } from 'eslint-etc'; -import * as path from 'path'; -import rule, { - noEffectDecorator, - noEffectDecoratorSuggest, -} from '../../src/rules/effects/no-effect-decorator'; -import { ruleTester } from '../utils'; - -type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule; -type Options = ESLintUtils.InferOptionsTypeFromRule; -type RunTests = TSESLint.RunTests; - -const valid: () => RunTests['valid'] = () => [ - ` -@Injectable() -export class FixtureEffects { - effectOK = createEffect(() => this.actions.pipe( - ofType('PING'), - map(() => ({ type: 'PONG' })) - )) - constructor(private actions: Actions) {} -}`, -]; - -const invalid: () => TSESLint.InvalidTestCase[] = () => [ - fromFixture( - ` -@Injectable() -export class FixtureEffects { - @Effect() - ~~~~~~~~~ [${noEffectDecorator}] - effect = this.actions.pipe( - ofType('PING'), - map(() => ({ type: 'PONG' })) - ) - constructor(private actions: Actions) {} -}`, - { - output: `import { createEffect } from '@ngrx/effects'; - -@Injectable() -export class FixtureEffects { - - effect = createEffect(() => { return this.actions.pipe( - ofType('PING'), - map(() => ({ type: 'PONG' })) - )}) - constructor(private actions: Actions) {} -}`, - } - ), - fromFixture( - ` -@Injectable() -export class FixtureEffects { - @Effect({ dispatch: true }) - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${noEffectDecorator}] - effect = this.actions.pipe( - ofType('PING'), - map(() => ({ type: 'PONG' })) - ) - constructor(private actions: Actions) {} -}`, - { - output: `import { createEffect } from '@ngrx/effects'; - -@Injectable() -export class FixtureEffects { - - effect = createEffect(() => { return this.actions.pipe( - ofType('PING'), - map(() => ({ type: 'PONG' })) - )}, { dispatch: true }) - constructor(private actions: Actions) {} -}`, - } - ), - fromFixture( - ` -import { createEffect, Effect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect({ dispatch: false }) - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${noEffectDecorator}] - effect = this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ) - constructor(private actions: Actions) {} -}`, - { - output: ` -import { createEffect, Effect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - - effect = createEffect(() => { return this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - )}, { dispatch: false }) - constructor(private actions: Actions) {} -}`, - } - ), - fromFixture( - ` -import { Injectable } from '@angular/core' -import { Effect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect(config) - ~~~~~~~~~~~~~~~ [${noEffectDecorator}] - effect = this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ) - constructor(private actions: Actions) {} -}`, - { - output: ` -import { Injectable } from '@angular/core' -import { Effect, createEffect } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - - effect = createEffect(() => { return this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - )}, config) - constructor(private actions: Actions) {} -}`, - } - ), - { - code: ` -import { Injectable } from '@angular/core' -import type { OnRunEffects } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect(config) - effect = this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ) - - @Effect({ dispatch: false }) - effect2 = createEffect(() => this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ), config) - constructor(private actions: Actions) {} -}`, - output: `import { createEffect } from '@ngrx/effects'; - -import { Injectable } from '@angular/core' -import type { OnRunEffects } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - - effect = createEffect(() => { return this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - )}, config) - - @Effect({ dispatch: false }) - effect2 = createEffect(() => this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ), config) - constructor(private actions: Actions) {} -}`, - errors: [ - { messageId: noEffectDecorator }, - { - column: 3, - endColumn: 31, - line: 12, - messageId: noEffectDecorator, - suggestions: [ - { - messageId: noEffectDecoratorSuggest as MessageIds, - output: ` -import { Injectable } from '@angular/core' -import type { OnRunEffects } from '@ngrx/effects' -@Injectable() -export class FixtureEffects { - @Effect(config) - effect = this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ) - - - effect2 = createEffect(() => this.actions.pipe( - ofType('PING'), - mapTo(CustomActions.pong()), - ), config) - constructor(private actions: Actions) {} -}`, - }, - ], - }, - ], - }, -]; - -ruleTester().run(path.parse(__filename).name, rule, { - valid: valid(), - invalid: invalid(), -}); diff --git a/modules/eslint-plugin/src/configs/all-requiring-type-checking.ts b/modules/eslint-plugin/src/configs/all-requiring-type-checking.ts index d123c43d80..5fc1875976 100644 --- a/modules/eslint-plugin/src/configs/all-requiring-type-checking.ts +++ b/modules/eslint-plugin/src/configs/all-requiring-type-checking.ts @@ -15,8 +15,6 @@ export = { '@ngrx/updater-explicit-return-type': 'warn', '@ngrx/avoid-cyclic-effects': 'warn', '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/no-multiple-actions-in-effects': 'warn', '@ngrx/prefer-action-creator-in-of-type': 'warn', diff --git a/modules/eslint-plugin/src/configs/all.ts b/modules/eslint-plugin/src/configs/all.ts index f9159158bf..6e38a5ac5e 100644 --- a/modules/eslint-plugin/src/configs/all.ts +++ b/modules/eslint-plugin/src/configs/all.ts @@ -10,8 +10,6 @@ export = { rules: { '@ngrx/updater-explicit-return-type': 'warn', '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/prefer-action-creator-in-of-type': 'warn', '@ngrx/prefer-concat-latest-from': 'warn', diff --git a/modules/eslint-plugin/src/configs/effects-requiring-type-checking.ts b/modules/eslint-plugin/src/configs/effects-requiring-type-checking.ts index 9c34605347..43008d8462 100644 --- a/modules/eslint-plugin/src/configs/effects-requiring-type-checking.ts +++ b/modules/eslint-plugin/src/configs/effects-requiring-type-checking.ts @@ -14,8 +14,6 @@ export = { rules: { '@ngrx/avoid-cyclic-effects': 'warn', '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/no-multiple-actions-in-effects': 'warn', '@ngrx/prefer-action-creator-in-of-type': 'warn', diff --git a/modules/eslint-plugin/src/configs/effects-strict-requiring-type-checking.ts b/modules/eslint-plugin/src/configs/effects-strict-requiring-type-checking.ts index f23e323e9e..bb537f15c6 100644 --- a/modules/eslint-plugin/src/configs/effects-strict-requiring-type-checking.ts +++ b/modules/eslint-plugin/src/configs/effects-strict-requiring-type-checking.ts @@ -14,8 +14,6 @@ export = { rules: { '@ngrx/avoid-cyclic-effects': 'error', '@ngrx/no-dispatch-in-effects': 'error', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'error', '@ngrx/no-effects-in-providers': 'error', '@ngrx/no-multiple-actions-in-effects': 'error', '@ngrx/prefer-action-creator-in-of-type': 'error', diff --git a/modules/eslint-plugin/src/configs/effects-strict.ts b/modules/eslint-plugin/src/configs/effects-strict.ts index 6055f72884..11a91b232e 100644 --- a/modules/eslint-plugin/src/configs/effects-strict.ts +++ b/modules/eslint-plugin/src/configs/effects-strict.ts @@ -9,8 +9,6 @@ export = { plugins: ['@ngrx'], rules: { '@ngrx/no-dispatch-in-effects': 'error', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'error', '@ngrx/no-effects-in-providers': 'error', '@ngrx/prefer-action-creator-in-of-type': 'error', '@ngrx/prefer-concat-latest-from': 'error', diff --git a/modules/eslint-plugin/src/configs/effects.ts b/modules/eslint-plugin/src/configs/effects.ts index b9f90df397..81fa3e946e 100644 --- a/modules/eslint-plugin/src/configs/effects.ts +++ b/modules/eslint-plugin/src/configs/effects.ts @@ -9,8 +9,6 @@ export = { plugins: ['@ngrx'], rules: { '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/prefer-action-creator-in-of-type': 'warn', '@ngrx/prefer-concat-latest-from': 'warn', diff --git a/modules/eslint-plugin/src/configs/recommended-requiring-type-checking.ts b/modules/eslint-plugin/src/configs/recommended-requiring-type-checking.ts index d123c43d80..5fc1875976 100644 --- a/modules/eslint-plugin/src/configs/recommended-requiring-type-checking.ts +++ b/modules/eslint-plugin/src/configs/recommended-requiring-type-checking.ts @@ -15,8 +15,6 @@ export = { '@ngrx/updater-explicit-return-type': 'warn', '@ngrx/avoid-cyclic-effects': 'warn', '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/no-multiple-actions-in-effects': 'warn', '@ngrx/prefer-action-creator-in-of-type': 'warn', diff --git a/modules/eslint-plugin/src/configs/recommended.ts b/modules/eslint-plugin/src/configs/recommended.ts index f9159158bf..6e38a5ac5e 100644 --- a/modules/eslint-plugin/src/configs/recommended.ts +++ b/modules/eslint-plugin/src/configs/recommended.ts @@ -10,8 +10,6 @@ export = { rules: { '@ngrx/updater-explicit-return-type': 'warn', '@ngrx/no-dispatch-in-effects': 'warn', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'warn', '@ngrx/no-effects-in-providers': 'error', '@ngrx/prefer-action-creator-in-of-type': 'warn', '@ngrx/prefer-concat-latest-from': 'warn', diff --git a/modules/eslint-plugin/src/configs/strict-requiring-type-checking.ts b/modules/eslint-plugin/src/configs/strict-requiring-type-checking.ts index cc6bde7002..189c0b48ab 100644 --- a/modules/eslint-plugin/src/configs/strict-requiring-type-checking.ts +++ b/modules/eslint-plugin/src/configs/strict-requiring-type-checking.ts @@ -15,8 +15,6 @@ export = { '@ngrx/updater-explicit-return-type': 'error', '@ngrx/avoid-cyclic-effects': 'error', '@ngrx/no-dispatch-in-effects': 'error', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'error', '@ngrx/no-effects-in-providers': 'error', '@ngrx/no-multiple-actions-in-effects': 'error', '@ngrx/prefer-action-creator-in-of-type': 'error', diff --git a/modules/eslint-plugin/src/configs/strict.ts b/modules/eslint-plugin/src/configs/strict.ts index a3dc0d44cc..4df5cc5776 100644 --- a/modules/eslint-plugin/src/configs/strict.ts +++ b/modules/eslint-plugin/src/configs/strict.ts @@ -10,8 +10,6 @@ export = { rules: { '@ngrx/updater-explicit-return-type': 'error', '@ngrx/no-dispatch-in-effects': 'error', - '@ngrx/no-effect-decorator-and-creator': 'error', - '@ngrx/no-effect-decorator': 'error', '@ngrx/no-effects-in-providers': 'error', '@ngrx/prefer-action-creator-in-of-type': 'error', '@ngrx/prefer-concat-latest-from': 'error', diff --git a/modules/eslint-plugin/src/rules/effects/no-effect-decorator-and-creator.ts b/modules/eslint-plugin/src/rules/effects/no-effect-decorator-and-creator.ts deleted file mode 100644 index 1b1403475a..0000000000 --- a/modules/eslint-plugin/src/rules/effects/no-effect-decorator-and-creator.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; -import * as path from 'path'; -import { createRule } from '../../rule-creator'; -import { - effectCreator, - effectDecorator, - getDecorator, - getDecoratorArguments, - getImportDeclarations, - getImportRemoveFix, - NGRX_MODULE_PATHS, -} from '../../utils'; - -export const noEffectDecoratorAndCreator = 'noEffectDecoratorAndCreator'; -export const noEffectDecoratorAndCreatorSuggest = - 'noEffectDecoratorAndCreatorSuggest'; - -type MessageIds = - | typeof noEffectDecoratorAndCreator - | typeof noEffectDecoratorAndCreatorSuggest; -type Options = readonly []; - -export default createRule({ - name: path.parse(__filename).name, - meta: { - type: 'suggestion', - hasSuggestions: true, - ngrxModule: 'effects', - docs: { - description: - '`Effect` should use either the `createEffect` or the `@Effect` decorator, but not both.', - recommended: 'error', - suggestion: true, - }, - fixable: 'code', - schema: [], - messages: { - [noEffectDecoratorAndCreator]: - 'Using the `createEffect` and the `@Effect` decorator simultaneously is forbidden.', - [noEffectDecoratorAndCreatorSuggest]: 'Remove the `@Effect` decorator.', - }, - }, - defaultOptions: [], - create: (context) => { - const sourceCode = context.getSourceCode(); - - return { - [`${effectCreator}:has(${effectDecorator})`]( - node: TSESTree.PropertyDefinition - ) { - const decorator = getDecorator(node, 'Effect'); - - if (!decorator) { - return; - } - - const hasDecoratorArgument = Boolean( - getDecoratorArguments(decorator)[0] - ); - const fix: TSESLint.ReportFixFunction = (fixer) => - getFixes(node, sourceCode, fixer, decorator); - - if (hasDecoratorArgument) { - context.report({ - node: node.key, - messageId: noEffectDecoratorAndCreator, - // In this case where the argument to the `@Effect({...})` - // decorator exists, it is more appropriate to **suggest** - // instead of **fix**, since either simply removing or merging - // the arguments would likely generate unexpected behaviors and - // would be quite costly. - suggest: [ - { - messageId: noEffectDecoratorAndCreatorSuggest, - fix, - }, - ], - }); - } else { - context.report({ - node: node.key, - messageId: noEffectDecoratorAndCreator, - fix, - }); - } - }, - }; - }, -}); - -function getFixes( - node: TSESTree.PropertyDefinition, - sourceCode: Readonly, - fixer: TSESLint.RuleFixer, - decorator: TSESTree.Decorator -): readonly TSESLint.RuleFix[] { - const importDeclarations = - getImportDeclarations(node, NGRX_MODULE_PATHS.effects) ?? []; - const text = sourceCode.getText(); - const totalEffectDecoratorOccurrences = getEffectDecoratorOccurrences(text); - const importRemoveFix = - totalEffectDecoratorOccurrences === 1 - ? getImportRemoveFix(sourceCode, importDeclarations, 'Effect', fixer) - : []; - - return [fixer.remove(decorator)].concat(importRemoveFix); -} - -function getEffectDecoratorOccurrences(text: string) { - return text.replace(/\s/g, '').match(/@Effect/g)?.length ?? 0; -} diff --git a/modules/eslint-plugin/src/rules/effects/no-effect-decorator.ts b/modules/eslint-plugin/src/rules/effects/no-effect-decorator.ts deleted file mode 100644 index a858347b38..0000000000 --- a/modules/eslint-plugin/src/rules/effects/no-effect-decorator.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; -import * as path from 'path'; -import { createRule } from '../../rule-creator'; -import { - getDecoratorArguments, - getImportAddFix, - isIdentifier, - NGRX_MODULE_PATHS, - propertyDefinitionWithEffectDecorator, -} from '../../utils'; - -export const noEffectDecorator = 'noEffectDecorator'; -export const noEffectDecoratorSuggest = 'noEffectDecoratorSuggest'; - -type MessageIds = typeof noEffectDecorator | typeof noEffectDecoratorSuggest; -type Options = readonly []; -type EffectDecorator = TSESTree.Decorator & { - parent: TSESTree.PropertyDefinition & { - parent: TSESTree.ClassBody & { parent: TSESTree.ClassDeclaration }; - value: TSESTree.CallExpression; - }; -}; - -const createEffectKeyword = 'createEffect'; - -export default createRule({ - name: path.parse(__filename).name, - meta: { - type: 'suggestion', - hasSuggestions: true, - ngrxModule: 'effects', - docs: { - description: `The \`${createEffectKeyword}\` is preferred as the \`@Effect\` decorator is deprecated.`, - recommended: 'warn', - suggestion: true, - }, - fixable: 'code', - schema: [], - messages: { - [noEffectDecorator]: `The \`@Effect\` decorator is deprecated. Use \`${createEffectKeyword}\` instead.`, - [noEffectDecoratorSuggest]: `Remove the \`@Effect\` decorator.`, - }, - }, - defaultOptions: [], - create: (context) => { - const sourceCode = context.getSourceCode(); - - return { - [propertyDefinitionWithEffectDecorator](node: EffectDecorator) { - const isUsingEffectCreator = - isIdentifier(node.parent.value.callee) && - node.parent.value.callee.name === createEffectKeyword; - - if (isUsingEffectCreator) { - context.report({ - node, - messageId: noEffectDecorator, - suggest: [ - { - messageId: noEffectDecoratorSuggest, - fix: (fixer) => fixer.remove(node), - }, - ], - }); - } else { - context.report({ - node, - messageId: noEffectDecorator, - fix: (fixer) => getFixes(node, sourceCode, fixer), - }); - } - }, - }; - }, -}); - -function getCreateEffectFix( - fixer: TSESLint.RuleFixer, - propertyValueExpression: TSESTree.CallExpression -): TSESLint.RuleFix { - return fixer.insertTextBefore( - propertyValueExpression, - `${createEffectKeyword}(() => { return ` - ); -} - -function getCreateEffectConfigFix( - fixer: TSESLint.RuleFixer, - propertyValueExpression: TSESTree.CallExpression, - configText?: string -): TSESLint.RuleFix { - const append = configText ? `, ${configText}` : ''; - return fixer.insertTextAfter(propertyValueExpression, `}${append})`); -} - -function getFixes( - node: EffectDecorator, - sourceCode: Readonly, - fixer: TSESLint.RuleFixer -): readonly TSESLint.RuleFix[] { - const classDeclaration = node.parent.parent.parent; - const { - parent: { value: propertyValueExpression }, - } = node; - - const [decoratorArgument] = getDecoratorArguments(node); - const configText = decoratorArgument - ? sourceCode.getText(decoratorArgument) - : undefined; - - return [ - fixer.remove(node), - getCreateEffectFix(fixer, propertyValueExpression), - getCreateEffectConfigFix(fixer, propertyValueExpression, configText), - ].concat( - getImportAddFix({ - fixer, - importName: createEffectKeyword, - moduleName: NGRX_MODULE_PATHS.effects, - node: classDeclaration, - }) - ); -} diff --git a/projects/ngrx.io/content/guide/eslint-plugin/index.md b/projects/ngrx.io/content/guide/eslint-plugin/index.md index 480c1872a5..824108f295 100644 --- a/projects/ngrx.io/content/guide/eslint-plugin/index.md +++ b/projects/ngrx.io/content/guide/eslint-plugin/index.md @@ -25,8 +25,6 @@ Some rules also allow automatic fixes with `ng lint --fix`. | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | ------- | --------------- | ------------ | ------------------------- | | [@ngrx/avoid-cyclic-effects](/guide/eslint-plugin/rules/avoid-cyclic-effects) | Avoid `Effect` that re-emit filtered actions. | problem | warn | No | No | No | Yes | | [@ngrx/no-dispatch-in-effects](/guide/eslint-plugin/rules/no-dispatch-in-effects) | `Effect` should not call `store.dispatch`. | suggestion | warn | No | Yes | No | No | -| [@ngrx/no-effect-decorator-and-creator](/guide/eslint-plugin/rules/no-effect-decorator-and-creator) | `Effect` should use either the `createEffect` or the `@Effect` decorator, but not both. | suggestion | error | Yes | Yes | No | No | -| [@ngrx/no-effect-decorator](/guide/eslint-plugin/rules/no-effect-decorator) | The `createEffect` is preferred as the `@Effect` decorator is deprecated. | suggestion | warn | Yes | Yes | No | No | | [@ngrx/no-effects-in-providers](/guide/eslint-plugin/rules/no-effects-in-providers) | `Effect` should not be listed as a provider if it is added to the `EffectsModule`. | problem | error | Yes | No | No | No | | [@ngrx/no-multiple-actions-in-effects](/guide/eslint-plugin/rules/no-multiple-actions-in-effects) | `Effect` should not return multiple actions. | problem | warn | No | No | No | Yes | | [@ngrx/prefer-action-creator-in-of-type](/guide/eslint-plugin/rules/prefer-action-creator-in-of-type) | Using `action creator` in `ofType` is preferred over `string`. | suggestion | warn | No | No | No | No | diff --git a/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator-and-creator.md b/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator-and-creator.md deleted file mode 100644 index 08d49bbd57..0000000000 --- a/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator-and-creator.md +++ /dev/null @@ -1,49 +0,0 @@ -# no-effect-decorator-and-creator - -`Effect` should use either the `createEffect` or the `@Effect` decorator, but not both. - -- **Type**: suggestion -- **Recommended**: Yes -- **Fixable**: Yes -- **Suggestion**: Yes -- **Requires type checking**: No -- **Configurable**: No - - - - -## Rule Details - -There are two ways we can register an Effect in NgRx. One is using the `@Effect` decorator (this is currently deprecated), the other is with the help of the `createEffect`. Using both simultaneously will result in the Effect being registered twice, and the side-effect will be performed twice every time the corresponding action is dispatched. - -Examples of **incorrect** code for this rule: - -```ts -export class Effects { - @Effect() loadData$ = createEffect(() => - this.actions$.pipe( - ofType(loadData) - // performing the side effect - ) - ); - - constructor(private readonly actions$: Actions) {} -} -``` - -Examples of **correct** code for this rule: - -```ts -export class Effects { - loadData$ = createEffect(() => { - return this.actions$.pipe( - ofType(loadData), - // performing the side effect - )) - }; - - constructor( - private readonly actions$: Actions, - ) {} -} -``` diff --git a/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator.md b/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator.md deleted file mode 100644 index d1d233f215..0000000000 --- a/projects/ngrx.io/content/guide/eslint-plugin/rules/no-effect-decorator.md +++ /dev/null @@ -1,48 +0,0 @@ -# no-effect-decorator - -The `createEffect` is preferred as the `@Effect` decorator is deprecated. - -- **Type**: suggestion -- **Recommended**: Yes -- **Fixable**: Yes -- **Suggestion**: Yes -- **Requires type checking**: No -- **Configurable**: No - - - - -## Rule Details - -The `@Effect` decorator is deprecated. There is no standardized support for decorators in ECMAScript, and Angular uses its internal build system to compile code with decorators, essentially using them as code annotations, so it is not sustainable to rely on decorators in other features. Instead, use the `createEffect` to create `Effects`. - -Examples of **incorrect** code for this rule: - -```ts -export class Effects { - @Effect() loadData$ = this.actions$.pipe( - ofType(loadData) - // performing the side effect - ); - - constructor(private readonly actions$: Actions) {} -} -``` - -Examples of **correct** code for this rule: - -```ts -export class Effects { - - loadData$ = createEffect(() => { - return this.actions$.pipe( - ofType(loadData), - // performing the side effect - )) - }; - - constructor( - private readonly actions$: Actions, - ) {} -} -```