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

Commit

Permalink
feat: add use-selector-in-select rule (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsaguet committed May 27, 2020
1 parent f984750 commit 59c137d
Show file tree
Hide file tree
Showing 5 changed files with 189 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 @@ -9,6 +9,7 @@
"ngrx/no-multiple-stores": "error",
"ngrx/no-dispatch-in-effects": "error",
"ngrx/no-effect-decorator": "error",
"ngrx/no-effects-in-providers": "error"
"ngrx/no-effects-in-providers": "error",
"ngrx/use-selector-in-select": "error"
}
}
5 changes: 5 additions & 0 deletions src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import noEffectDecorator, {
import noEffectsInProviders, {
ruleName as noEffectsInProvidersRuleName,
} from './no-effects-in-providers'
import useSelectorInSelect, {
ruleName as useSelectorInSelectRuleName,
} from './use-selector-in-select'

const ruleNames = {
actionHygieneRuleName,
Expand All @@ -36,6 +39,7 @@ const ruleNames = {
noDispatchInEffectsRuleName,
noEffectDecoratorRuleName,
noEffectsInProvidersRuleName,
useSelectorInSelectRuleName,
}

export const rules = {
Expand All @@ -48,4 +52,5 @@ export const rules = {
[ruleNames.noDispatchInEffectsRuleName]: noDispatchInEffects,
[ruleNames.noEffectDecoratorRuleName]: noEffectDecorator,
[ruleNames.noEffectsInProvidersRuleName]: noEffectsInProviders,
[ruleNames.useSelectorInSelectRuleName]: useSelectorInSelect,
}
42 changes: 42 additions & 0 deletions src/rules/use-selector-in-select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'

import { select } from './utils'

export const ruleName = 'use-selector-in-select'

export const messageId = 'useSelectorInSelect'
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:
'Using a selector in a select function is preferred in favor of strings/props drilling',
extraDescription: [
'A selector is more performant, shareable and maintainable',
],
recommended: 'error',
},
schema: [],
messages: {
[messageId]:
'Using string or props drilling is not preferred, use a selector instead',
},
},
defaultOptions: [],
create: context => {
return {
[select](node: TSESTree.Literal | TSESTree.ArrowFunctionExpression) {
context.report({
node,
messageId,
})
},
}
},
})
5 changes: 5 additions & 0 deletions src/rules/utils/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ export const ngModuleDecorator = `ClassDeclaration > Decorator > CallExpression[
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`

const pipeableSelect = `CallExpression[callee.property.name="pipe"] CallExpression[callee.name="select"]`
const storeSelect = `CallExpression[callee.object.name='store'][callee.property.name='select']`

export const select = `${pipeableSelect} Literal, ${storeSelect} Literal, ${pipeableSelect} ArrowFunctionExpression, ${storeSelect} ArrowFunctionExpression`
135 changes: 135 additions & 0 deletions tests/rules/use-selector-in-select.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { stripIndent } from 'common-tags'
import rule, {
ruleName,
messageId,
} from '../../src/rules/use-selector-in-select'
import { ruleTester } from '../utils'

ruleTester().run(ruleName, rule, {
valid: [
`store.pipe(select(selectCustomers))`,
`store.pipe(select(selectorsObj.selectCustomers))`,
`store.select(selectCustomers)`,
`store.select(selectorsObj.selectCustomers)`,
],
invalid: [
{
code: stripIndent`
store.pipe(select('customers'))`,
errors: [
{
messageId,
line: 1,
column: 19,
endLine: 1,
endColumn: 30,
},
],
},
{
code: stripIndent`
store.select('customers')`,
errors: [
{
messageId,
line: 1,
column: 14,
endLine: 1,
endColumn: 25,
},
],
},
{
code: stripIndent`
store.pipe(select('customers', 'orders'))`,
errors: [
{
messageId,
line: 1,
column: 19,
endLine: 1,
endColumn: 30,
},
{
messageId,
line: 1,
column: 32,
endLine: 1,
endColumn: 40,
},
],
},
{
code: stripIndent`
store.select('customers', 'orders')`,
errors: [
{
messageId,
line: 1,
column: 14,
endLine: 1,
endColumn: 25,
},
{
messageId,
line: 1,
column: 27,
endLine: 1,
endColumn: 35,
},
],
},
{
code: stripIndent`
store.pipe(select(state => state.customers))`,
errors: [
{
messageId,
line: 1,
column: 19,
endLine: 1,
endColumn: 43,
},
],
},
{
code: stripIndent`
store.select(state => state.customers)`,
errors: [
{
messageId,
line: 1,
column: 14,
endLine: 1,
endColumn: 38,
},
],
},
{
code: stripIndent`
store.pipe(select(state => state.customers.orders))`,
errors: [
{
messageId,
line: 1,
column: 19,
endLine: 1,
endColumn: 50,
},
],
},
{
code: stripIndent`
store.select(state => state.customers.orders)`,
errors: [
{
messageId,
line: 1,
column: 14,
endLine: 1,
endColumn: 45,
},
],
},
],
})

0 comments on commit 59c137d

Please sign in to comment.