diff --git a/modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts b/modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts index e73840c680..dfcd03ad7e 100644 --- a/modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts +++ b/modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts @@ -64,6 +64,36 @@ export class Effects { ) } }`, + ` +export const saveSearchCriteria$ = createEffect( + (actions$ = inject(Actions$), store = inject(Store), saveLoadService = inject(SaveLoadService)) => { + return actions$.pipe( + ofType(SearchCriteriaActions.save), + concatLatestFrom(() => store.select(inventoryFeature.selectInventoryItems)), + concatMap(([{ searchCriteriaName }, inventoryItems]) => { + const tags = Object.keys(inventoryItems) + .filter((inventoryType) => { + const [, inventorySearchType] = splitInventoryType(inventoryType as InventoryType); + return inventorySearchType === 'costCenter' || inventorySearchType === 'wbs'; + }) + .map((inventoryType) => splitInventoryType(inventoryType as InventoryType)) + .map(([value, type]) => ({ value, type })); + return saveLoadService.saveSearch(searchCriteriaName, tags, false).pipe( + map(() => SearchCriteriaActions.saveSucceeded()), + catchError((error: Error) => { + if (error instanceof HttpErrorResponse && error.status === 409) { + return of(SearchCriteriaActions.saveAlreadyExists({ searchCriteriaName, tags })); + } + + return defaultErrorHadnler(error, 'inventoryDomain.messages.saveSearchFailed', SearchCriteriaActions.saveFailed()); + }) + ); + }) + ); + }, + { functional: true } +); + `, ]; const invalid: () => RunTests['invalid'] = () => [ diff --git a/modules/eslint-plugin/src/rules/effects/no-multiple-actions-in-effects.ts b/modules/eslint-plugin/src/rules/effects/no-multiple-actions-in-effects.ts index feb3caf426..0ceb175b1a 100644 --- a/modules/eslint-plugin/src/rules/effects/no-multiple-actions-in-effects.ts +++ b/modules/eslint-plugin/src/rules/effects/no-multiple-actions-in-effects.ts @@ -7,8 +7,9 @@ import * as path from 'path'; import { createRule } from '../../rule-creator'; import { createEffectExpression, - mapLikeOperatorsExplicitReturn, - mapLikeOperatorsImplicitReturn, + isBlockStatement, + isReturnStatement, + mapLikeOperatorCallExpressions, } from '../../utils'; export const messageId = 'noMultipleActionsInEffects'; @@ -18,6 +19,7 @@ type Options = readonly unknown[]; type EffectsMapLikeOperatorsReturn = | TSESTree.ArrowFunctionExpression | TSESTree.CallExpression + | TSESTree.FunctionExpression | TSESTree.ReturnStatement; export default createRule({ @@ -37,7 +39,7 @@ export default createRule({ defaultOptions: [], create: (context) => { return { - [`${createEffectExpression} :matches(${mapLikeOperatorsImplicitReturn}, ${mapLikeOperatorsExplicitReturn})`]( + [`${createEffectExpression} ${mapLikeOperatorCallExpressions}`]( node: EffectsMapLikeOperatorsReturn ) { const nodeToReport = getNodeToReport(node); @@ -71,10 +73,20 @@ export default createRule({ function getNodeToReport(node: EffectsMapLikeOperatorsReturn) { switch (node.type) { case AST_NODE_TYPES.ArrowFunctionExpression: - return node.body; + case AST_NODE_TYPES.FunctionExpression: + return isBlockStatement(node.body) + ? findReturnStatement(node.body.body) + : node.body; case AST_NODE_TYPES.CallExpression: - return node.arguments[0]; + return findReturnStatement(node.arguments) ?? node.arguments[0]; default: return node.argument; } } + +function findReturnStatement(nodes: TSESTree.Node[]) { + const returnNode = nodes.find((n): n is TSESTree.ReturnStatement => + isReturnStatement(n) + ); + return returnNode?.argument; +} diff --git a/modules/eslint-plugin/src/utils/helper-functions/guards.ts b/modules/eslint-plugin/src/utils/helper-functions/guards.ts index 11e7d71312..f11e21238a 100644 --- a/modules/eslint-plugin/src/utils/helper-functions/guards.ts +++ b/modules/eslint-plugin/src/utils/helper-functions/guards.ts @@ -47,7 +47,7 @@ export const isTSInstantiationExpression = isNodeOfType( ); export const isProperty = isNodeOfType(AST_NODE_TYPES.Property); export const isArrayExpression = isNodeOfType(AST_NODE_TYPES.ArrayExpression); - +export const isBlockStatement = isNodeOfType(AST_NODE_TYPES.BlockStatement); export function isIdentifierOrMemberExpression( node: TSESTree.Node ): node is TSESTree.Identifier | TSESTree.MemberExpression { diff --git a/modules/eslint-plugin/src/utils/selectors/index.ts b/modules/eslint-plugin/src/utils/selectors/index.ts index a80052c37e..988640e61b 100644 --- a/modules/eslint-plugin/src/utils/selectors/index.ts +++ b/modules/eslint-plugin/src/utils/selectors/index.ts @@ -78,7 +78,5 @@ export const actionReducerMap = `VariableDeclarator[id.typeAnnotation.typeAnnota const mapLikeOperators = '/^(concat|exhaust|flat|merge|switch)Map$/'; const mapLikeToOperators = '/^(concat|merge|switch)MapTo$/'; -export const mapLikeOperatorsExplicitReturn = - `CallExpression[callee.name=${mapLikeOperators}] ReturnStatement` as const; -export const mapLikeOperatorsImplicitReturn = - `:matches(CallExpression[callee.name=${mapLikeToOperators}], CallExpression[callee.name=${mapLikeOperators}] > ArrowFunctionExpression)` as const; +export const mapLikeOperatorCallExpressions = + `:matches(CallExpression[callee.name=${mapLikeToOperators}], CallExpression[callee.name=${mapLikeOperators}] > :matches(ReturnStatement,ArrowFunctionExpression,FunctionExpression))` as const;