diff --git a/src/rules/no-misused-observables.ts b/src/rules/no-misused-observables.ts index 9c7a9225..50303c9e 100644 --- a/src/rules/no-misused-observables.ts +++ b/src/rules/no-misused-observables.ts @@ -180,11 +180,21 @@ export const noMisusedObservablesRule = ruleCreator({ } function checkJSXAttribute(node: es.JSXAttribute): void { - if (!node.value || !isJSXExpressionContainer(node.value)) { + if ( + node.value == null + || !isJSXExpressionContainer(node.value) + ) { return; } - - if (couldReturnObservable(node.value.expression)) { + const expressionContainer = esTreeNodeToTSNodeMap.get( + node.value, + ); + const contextualType = checker.getContextualType(expressionContainer); + if ( + contextualType != null + && isVoidReturningFunctionType(contextualType) + && couldReturnObservable(node.value.expression) + ) { context.report({ messageId: 'forbiddenVoidReturnAttribute', node: node.value, @@ -336,8 +346,8 @@ export const noMisusedObservablesRule = ruleCreator({ const tsNode = esTreeNodeToTSNodeMap.get(node); if ( tsNode.initializer == null - || !node.init - || !node.id.typeAnnotation + || node.init == null + || node.id.typeAnnotation == null ) { return; } diff --git a/tests/rules/no-misused-observables.test.ts b/tests/rules/no-misused-observables.test.ts index 23737a32..d0f06da1 100644 --- a/tests/rules/no-misused-observables.test.ts +++ b/tests/rules/no-misused-observables.test.ts @@ -49,30 +49,43 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu { code: stripIndent` // void return attribute; explicitly allowed - import { Observable, of } from "rxjs"; - import React, { FC } from "react"; + import { of } from "rxjs"; - const Component: FC<{ foo: () => void }> = () =>
; - const App = () => { - return ( - of(42)} /> - ); - }; + interface Props { + foo: () => void; + } + declare function Component(props: Props): any; + + const _ = of(42)} />; `, options: [{ checksVoidReturn: { attributes: false } }], languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, }, + { + code: stripIndent` + // void return attribute; not void + import { Observable, of } from "rxjs"; + + interface Props { + foo: () => Observable; + } + declare function Component(props: Props): any; + + const _ = of(42)} />; + `, + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, + }, { code: stripIndent` // void return attribute; unrelated - import React, { FC } from "react"; - const Component: FC<{ foo: () => void, bar: boolean }> = () =>
; - const App = () => { - return ( - 42} bar /> - ); - }; + interface Props { + foo: () => void; + bar: boolean; + } + declare function Component(props: Props): any; + + const _ = 42} bar />; `, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, }, @@ -339,15 +352,14 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu stripIndent` // void return attribute; block body import { Observable, of } from "rxjs"; - import React, { FC } from "react"; - - const Component: FC<{ foo: () => void }> = () =>
; - const App = () => { - return ( - => { return of(42); }} /> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnAttribute] - ); - }; + + interface Props { + foo: () => void; + } + declare function Component(props: Props): any; + + const _ = => { return of(42); }} />; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbiddenVoidReturnAttribute] `, { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, @@ -357,15 +369,14 @@ ruleTester({ types: true }).run('no-misused-observables', noMisusedObservablesRu stripIndent` // void return attribute; inline body import { Observable, of } from "rxjs"; - import React, { FC } from "react"; - - const Component: FC<{ foo: () => void }> = () =>
; - const App = () => { - return ( - of(42)} /> - ~~~~~~~~~~~~~~ [forbiddenVoidReturnAttribute] - ); - }; + + interface Props { + foo: () => void; + } + declare function Component(props: Props): any; + + const _ = of(42)} />; + ~~~~~~~~~~~~~~ [forbiddenVoidReturnAttribute] `, { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },