From 52e2539af7a742f9c34149fec0a0cbf16018ec88 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Sat, 16 Nov 2024 12:56:44 -0600 Subject: [PATCH] fix(no-ignored-error): check observer object too --- src/rules/no-ignored-error.ts | 49 +++++++++++++++++++++++++--- tests/rules/no-ignored-error.test.ts | 29 ++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/rules/no-ignored-error.ts b/src/rules/no-ignored-error.ts index 01b5f357..b3657d45 100644 --- a/src/rules/no-ignored-error.ts +++ b/src/rules/no-ignored-error.ts @@ -1,5 +1,5 @@ -import { TSESTree as es } from '@typescript-eslint/utils'; -import { getTypeServices } from '../etc'; +import { TSESTree as es, ESLintUtils } from '@typescript-eslint/utils'; +import { getTypeServices, isIdentifier, isMemberExpression, isObjectExpression, isProperty } from '../etc'; import { ruleCreator } from '../utils'; export const noIgnoredErrorRule = ruleCreator({ @@ -18,8 +18,49 @@ export const noIgnoredErrorRule = ruleCreator({ }, name: 'no-ignored-error', create: (context) => { + const { getTypeAtLocation } = ESLintUtils.getParserServices(context); const { couldBeObservable, couldBeFunction } = getTypeServices(context); + function isMissingErrorCallback(callExpression: es.CallExpression): boolean { + if (callExpression.arguments.length >= 2) { + return false; + } + return couldBeFunction(callExpression.arguments[0]); + } + + function isObjMissingError(arg: es.ObjectExpression): boolean { + return !arg.properties.some( + property => + isProperty(property) + && isIdentifier(property.key) + && property.key.name === 'error', + ); + } + + function isTypeMissingError(arg: es.Identifier): boolean { + const argType = getTypeAtLocation(arg); + return !argType?.getProperties().some(p => p.name === 'error'); + } + + function isMissingErrorProperty(callExpression: es.CallExpression): boolean { + if (callExpression.arguments.length !== 1) { + return false; + } + + const [arg] = callExpression.arguments; + + if (isObjectExpression(arg)) { + return isObjMissingError(arg); + } + if (isIdentifier(arg)) { + return isTypeMissingError(arg); + } + if (isMemberExpression(arg) && isIdentifier(arg.property)) { + return isTypeMissingError(arg.property); + } + return false; + } + return { 'CallExpression[arguments.length > 0] > MemberExpression > Identifier[name=\'subscribe\']': (node: es.Identifier) => { @@ -27,9 +68,9 @@ export const noIgnoredErrorRule = ruleCreator({ const callExpression = memberExpression.parent as es.CallExpression; if ( - callExpression.arguments.length < 2 + (isMissingErrorCallback(callExpression) + || isMissingErrorProperty(callExpression)) && couldBeObservable(memberExpression.object) - && couldBeFunction(callExpression.arguments[0]) ) { context.report({ messageId: 'forbidden', diff --git a/tests/rules/no-ignored-error.test.ts b/tests/rules/no-ignored-error.test.ts index 69bd350b..49a46028 100644 --- a/tests/rules/no-ignored-error.test.ts +++ b/tests/rules/no-ignored-error.test.ts @@ -11,6 +11,19 @@ ruleTester({ types: true }).run('no-ignored-error', noIgnoredErrorRule, { const observable = of([1, 2]); observable.subscribe(() => {}, () => {}); `, + stripIndent` + // observer argument + import { of } from "rxjs"; + + const observable = of([1, 2]); + observable.subscribe({ next: () => {}, error: () => {} }); + + const observer1 = { next: () => {}, error: () => {} }; + observable.subscribe(observer1); + + const obj = { observer: observer1 }; + observable.subscribe(obj.observer); + `, stripIndent` // subject import { Subject } from "rxjs"; @@ -85,5 +98,21 @@ ruleTester({ types: true }).run('no-ignored-error', noIgnoredErrorRule, { } `, ), + fromFixture( + stripIndent` + // observer argument + import { of } from "rxjs"; + + const observable = of([1, 2]); + observable.subscribe({ next: () => {} }); + ~~~~~~~~~ [forbidden] + const observer1 = { next: () => {} }; + observable.subscribe(observer1); + ~~~~~~~~~ [forbidden] + const obj = { observer: observer1 }; + observable.subscribe(obj.observer1); + ~~~~~~~~~ [forbidden] + `, + ), ], });