|
| 1 | +import { |
| 2 | + TSESTree as es, |
| 3 | +} from '@typescript-eslint/utils'; |
| 4 | +import { MULTIPLE_OBSERVABLE_ACCEPTING_STATIC_OBSERVABLE_CREATORS, SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS } from '../constants'; |
| 5 | +import { getTypeServices, isArrayExpression, isObjectExpression, isProperty } from '../etc'; |
| 6 | +import { ruleCreator } from '../utils'; |
| 7 | + |
| 8 | +export const noUnnecessaryCollectionRule = ruleCreator({ |
| 9 | + defaultOptions: [], |
| 10 | + meta: { |
| 11 | + docs: { |
| 12 | + description: 'Disallow unnecessary usage of collection arguments with single values.', |
| 13 | + recommended: 'strict', |
| 14 | + requiresTypeChecking: false, |
| 15 | + }, |
| 16 | + messages: { |
| 17 | + forbidden: 'Unnecessary {{operator}} with {{inputType}}. Use the observable directly instead.', |
| 18 | + }, |
| 19 | + schema: [], |
| 20 | + type: 'suggestion', |
| 21 | + }, |
| 22 | + name: 'no-unnecessary-collection', |
| 23 | + create: (context) => { |
| 24 | + const { couldBeType, couldBeObservable } = getTypeServices(context); |
| 25 | + |
| 26 | + function couldBeFromRxjs(node: es.Node, operatorName: string): boolean { |
| 27 | + return couldBeType(node, operatorName, { name: /[/\\]rxjs[/\\]/ }); |
| 28 | + } |
| 29 | + |
| 30 | + function checkCallExpression(node: es.CallExpression, operatorName: string): void { |
| 31 | + const args = node.arguments; |
| 32 | + |
| 33 | + if (args.length === 0) { |
| 34 | + return; |
| 35 | + } |
| 36 | + |
| 37 | + const firstArg = args[0]; |
| 38 | + |
| 39 | + // Single-valued array. |
| 40 | + if (isArrayExpression(firstArg)) { |
| 41 | + const nonNullElements = firstArg.elements.filter(element => element !== null); |
| 42 | + if (nonNullElements.length === 1 && couldBeObservable(nonNullElements[0])) { |
| 43 | + context.report({ |
| 44 | + messageId: 'forbidden', |
| 45 | + node: node.callee, |
| 46 | + data: { |
| 47 | + operator: operatorName, |
| 48 | + inputType: 'single-valued array', |
| 49 | + }, |
| 50 | + }); |
| 51 | + } |
| 52 | + return; |
| 53 | + } |
| 54 | + |
| 55 | + // Single-property object. |
| 56 | + if (isObjectExpression(firstArg) && SOURCES_OBJECT_ACCEPTING_STATIC_OBSERVABLE_CREATORS.includes(operatorName)) { |
| 57 | + if (firstArg.properties.length === 1 && isProperty(firstArg.properties[0])) { |
| 58 | + const property = firstArg.properties[0]; |
| 59 | + if (property.value && couldBeObservable(property.value)) { |
| 60 | + context.report({ |
| 61 | + messageId: 'forbidden', |
| 62 | + node: node.callee, |
| 63 | + data: { |
| 64 | + operator: operatorName, |
| 65 | + inputType: 'single-property object', |
| 66 | + }, |
| 67 | + }); |
| 68 | + } |
| 69 | + } |
| 70 | + return; |
| 71 | + } |
| 72 | + |
| 73 | + // Single rest parameter argument. |
| 74 | + if (args.length === 1 && couldBeObservable(firstArg)) { |
| 75 | + context.report({ |
| 76 | + messageId: 'forbidden', |
| 77 | + node: node.callee, |
| 78 | + data: { |
| 79 | + operator: operatorName, |
| 80 | + inputType: 'single argument', |
| 81 | + }, |
| 82 | + }); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + const callExpressionVisitors: Record<string, (node: es.CallExpression) => void> = {}; |
| 87 | + |
| 88 | + for (const operator of MULTIPLE_OBSERVABLE_ACCEPTING_STATIC_OBSERVABLE_CREATORS) { |
| 89 | + callExpressionVisitors[`CallExpression[callee.name="${operator}"]`] = (node: es.CallExpression) => { |
| 90 | + if (couldBeFromRxjs(node.callee, operator)) { |
| 91 | + checkCallExpression(node, operator); |
| 92 | + } |
| 93 | + }; |
| 94 | + } |
| 95 | + |
| 96 | + for (const operator of MULTIPLE_OBSERVABLE_ACCEPTING_STATIC_OBSERVABLE_CREATORS) { |
| 97 | + callExpressionVisitors[`CallExpression[callee.type="MemberExpression"][callee.property.name="${operator}"]`] = (node: es.CallExpression) => { |
| 98 | + const memberExpr = node.callee as es.MemberExpression; |
| 99 | + if (couldBeFromRxjs(memberExpr.property, operator)) { |
| 100 | + checkCallExpression(node, operator); |
| 101 | + } |
| 102 | + }; |
| 103 | + } |
| 104 | + |
| 105 | + return callExpressionVisitors; |
| 106 | + }, |
| 107 | +}); |
0 commit comments