Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

Commit

Permalink
feat: add no-effects-in-providers rule (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsaguet authored May 27, 2020
1 parent 6205d40 commit f984750
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/configs/recommended.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"ngrx/avoid-dispatching-multiple-actions-sequentially": "error",
"ngrx/no-multiple-stores": "error",
"ngrx/no-dispatch-in-effects": "error",
"ngrx/no-effect-decorator": "error"
"ngrx/no-effect-decorator": "error",
"ngrx/no-effects-in-providers": "error"
}
}
5 changes: 5 additions & 0 deletions src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import noDispatchInEffects, {
import noEffectDecorator, {
ruleName as noEffectDecoratorRuleName,
} from './no-effect-decorator'
import noEffectsInProviders, {
ruleName as noEffectsInProvidersRuleName,
} from './no-effects-in-providers'

const ruleNames = {
actionHygieneRuleName,
Expand All @@ -32,6 +35,7 @@ const ruleNames = {
noMultipleStoresRuleName,
noDispatchInEffectsRuleName,
noEffectDecoratorRuleName,
noEffectsInProvidersRuleName,
}

export const rules = {
Expand All @@ -43,4 +47,5 @@ export const rules = {
[ruleNames.noMultipleStoresRuleName]: noMultipleStores,
[ruleNames.noDispatchInEffectsRuleName]: noDispatchInEffects,
[ruleNames.noEffectDecoratorRuleName]: noEffectDecorator,
[ruleNames.noEffectsInProvidersRuleName]: noEffectsInProviders,
}
59 changes: 59 additions & 0 deletions src/rules/no-effects-in-providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'

import {
ngModuleDecorator,
ngModuleImports,
ngModuleProviders,
} from './utils/selectors'

export const ruleName = 'no-effects-in-providers'

export const messageId = 'noEffectsInProviders'
export type MessageIds = typeof messageId

type Options = []

export default ESLintUtils.RuleCreator(name => name)<Options, MessageIds>({
name: ruleName,
meta: {
type: 'problem',
docs: {
category: 'Possible Errors',
description:
'An Effect should not be listed as a provider if it is added to the EffectsModule',
extraDescription: [
'If an Effect is registered with EffectsModule and is added as a provider, it will be registered twice',
],
recommended: 'error',
},
schema: [],
messages: {
[messageId]:
'The Effect should not be listed as a provider if it is added to the EffectsModule',
},
},
defaultOptions: [],
create: context => {
const effectsInProviders: TSESTree.Identifier[] = []
const importedEffectsNames: string[] = []

return {
[ngModuleProviders](node: TSESTree.Identifier) {
effectsInProviders.push(node)
},
[ngModuleImports](node: TSESTree.Identifier) {
importedEffectsNames.push(node.name)
},
[`${ngModuleDecorator}:exit`]() {
effectsInProviders.forEach((effect: TSESTree.Identifier) => {
if (importedEffectsNames.includes(effect.name)) {
context.report({
node: effect,
messageId,
})
}
})
},
}
},
})
6 changes: 6 additions & 0 deletions src/rules/utils/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ export const dispatchInEffects = `ClassProperty > CallExpression:has(Identifier[
export const injectedStore = `MethodDefinition[kind='constructor'] Identifier>TSTypeAnnotation>TSTypeReference[typeName.name="Store"]`

export const typedStore = `${injectedStore}[typeParameters.params]`

export const ngModuleDecorator = `ClassDeclaration > Decorator > CallExpression[callee.name='NgModule']`

export const ngModuleProviders = `${ngModuleDecorator} ObjectExpression Property[key.name='providers'] > ArrayExpression Identifier`

export const ngModuleImports = `${ngModuleDecorator} ObjectExpression Property[key.name='imports'] > ArrayExpression CallExpression[callee.object.name='EffectsModule'][callee.property.name=/forRoot|forFeature/] ArrayExpression > Identifier`
95 changes: 95 additions & 0 deletions tests/rules/no-effects-in-providers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { stripIndent } from 'common-tags'
import rule, {
ruleName,
messageId,
} from '../../src/rules/no-effects-in-providers'
import { ruleTester } from '../utils'

ruleTester().run(ruleName, rule, {
valid: [
`
@NgModule({
imports: [
StoreModule.forFeature('persons', {"foo": "bar"}),
EffectsModule.forRoot([RootEffectOne]),
EffectsModule.forFeature([FeatEffectOne]),
],
providers: [FeatEffectTwo, UnRegisteredEffect, FeatEffectThree, RootEffectTwo],
})
export class AppModule {}`,
],
invalid: [
{
code: stripIndent`
@NgModule({
imports: [
StoreModule.forFeature('persons', {"foo": "bar"}),
EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),
EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),
EffectsModule.forFeature([FeatEffectThree]),
],
providers: [FeatEffectTwo, UnRegisteredEffect, FeatEffectThree, RootEffectTwo],
})
export class AppModule {}`,
errors: [
{
messageId,
line: 8,
column: 15,
endLine: 8,
endColumn: 28,
},
{
messageId,
line: 8,
column: 50,
endLine: 8,
endColumn: 65,
},
{
messageId,
line: 8,
column: 67,
endLine: 8,
endColumn: 80,
},
],
},
{
code: stripIndent`
@NgModule({
providers: [FeatEffectTwo, UnRegisteredEffect, FeatEffectThree, RootEffectTwo],
imports: [
StoreModule.forFeature('persons', {"foo": "bar"}),
EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),
EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),
EffectsModule.forFeature([FeatEffectThree]),
],
})
export class AppModule {}`,
errors: [
{
messageId,
line: 2,
column: 15,
endLine: 2,
endColumn: 28,
},
{
messageId,
line: 2,
column: 50,
endLine: 2,
endColumn: 65,
},
{
messageId,
line: 2,
column: 67,
endLine: 2,
endColumn: 80,
},
],
},
],
})

0 comments on commit f984750

Please sign in to comment.