diff --git a/src/rules/no-misused-observables.ts b/src/rules/no-misused-observables.ts index 1b28b03b..ac4d06d7 100644 --- a/src/rules/no-misused-observables.ts +++ b/src/rules/no-misused-observables.ts @@ -11,6 +11,7 @@ import { isJSXExpressionContainer, isMethodDefinition, isPropertyDefinition, + couldBeType as tsutilsEtcCouldBeType, } from '../etc'; import { ruleCreator } from '../utils'; @@ -418,6 +419,12 @@ function isVoidReturningFunctionType( for (const signature of subType.getCallSignatures()) { const returnType = signature.getReturnType(); + // If a certain positional argument accepts both Observable and void returns, + // an Observable-returning function is valid. + if (tsutilsEtcCouldBeType(returnType, 'Observable')) { + return false; + } + hasVoidReturn ||= tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void); } } diff --git a/tests/rules/no-misused-observables.test.ts b/tests/rules/no-misused-observables.test.ts index 5f93c8aa..bc7f0696 100644 --- a/tests/rules/no-misused-observables.test.ts +++ b/tests/rules/no-misused-observables.test.ts @@ -233,6 +233,18 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu bar, }; `, + stripIndent` + // void return property; union type + import { Observable, of } from "rxjs"; + + interface Hook { + onInit?: ((field: string) => void) | ((field: string) => Observable); + } + + const hook: Hook = { + onInit: field => of(field), + }; + `, // #endregion valid; void return property // #region valid; void return return value {