diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx
index 52dc08dfcef6..95c4df77af35 100644
--- a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx
+++ b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx
@@ -311,4 +311,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates
## Related To
-- [`no-floating-promises`](./no-floating-promises.mdx)
+- [`strict-void-return`](./strict-void-return.mdx) - A superset of this rule's `checksVoidReturn` option which also checks for non-Promise values.
+- [`no-floating-promises`](./no-floating-promises.mdx) - Warns about unhandled promises in _statement_ positions.
diff --git a/packages/eslint-plugin/docs/rules/strict-void-return.mdx b/packages/eslint-plugin/docs/rules/strict-void-return.mdx
new file mode 100644
index 000000000000..4991441145c3
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/strict-void-return.mdx
@@ -0,0 +1,321 @@
+---
+description: 'Disallow passing a value-returning function in a position accepting a void function.'
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+> 🛑 This file is source code, not the primary documentation location! 🛑
+>
+> See **https://typescript-eslint.io/rules/strict-void-return** for documentation.
+
+## Rule Details
+
+TypeScript considers functions returning a value to be assignable to a function returning void.
+Using this feature of TypeScript can lead to bugs or confusing code.
+
+## Examples
+
+### Return type unsafety
+
+Passing a value-returning function in a place expecting a void function can be unsound.
+TypeScript generally treats the `void` type as though it has the same runtime behavior as `undefined`,
+so this pattern will cause a mismatch between the runtime behavior and the types.
+
+```ts
+// TypeScript errors on overtly wrong ways of populating the `void` type...
+function returnsVoid(): void {
+ return 1234; // TS Error: Type 'number' is not assignable to type 'void'.
+}
+
+// ...but allows more subtle ones
+const returnsVoid: () => void = () => 1234;
+
+// Likewise, TypeScript errors on overtly wrong usages of `void` as a runtime value...
+declare const v: void;
+if (v) {
+ // TS Error: An expression of type 'void' cannot be tested for truthiness.
+ // ... do something
+}
+
+// ...but silently treats `void` as `undefined` in more subtle scenarios
+declare const voidOrString: void | string;
+if (voidOrString) {
+ // voidOrString is narrowed to string in this branch, so this is allowed.
+ console.log(voidOrString.toUpperCase());
+}
+```
+
+Between these two behaviors, examples like the following will throw at runtime, despite not reporting a type error:
+
+
+
+
+```ts
+const getNothing: () => void = () => 2137;
+const getString: () => string = () => 'Hello';
+const maybeString = Math.random() > 0.1 ? getNothing() : getString();
+if (maybeString) console.log(maybeString.toUpperCase()); // ❌ Crash if getNothing was called
+```
+
+
+
+
+```ts
+const getNothing: () => void = () => {};
+const getString: () => string = () => 'Hello';
+const maybeString = Math.random() > 0.1 ? getNothing() : getString();
+if (maybeString) console.log(maybeString.toUpperCase()); // ✅ No crash
+```
+
+
+
+
+### Unhandled returned promises
+
+If a callback is meant to return void, values returned from functions are likely ignored.
+Ignoring a returned Promise means any Promise rejection will be silently ignored
+or crash the process depending on runtime.
+
+
+
+
+```ts
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(async () => {
+ const response = await fetch('https://api.example.com/');
+ const data = await response.json();
+ console.log(data);
+});
+```
+
+
+
+
+```ts
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(() => {
+ (async () => {
+ const response = await fetch('https://api.example.com/');
+ const data = await response.json();
+ console.log(data);
+ })().catch(console.error);
+});
+```
+
+
+
+
+:::info
+If you only care about promises,
+you can use the [`no-misused-promises`](no-misused-promises.mdx) rule instead.
+:::
+
+:::tip
+Use [`no-floating-promises`](no-floating-promises.mdx)
+to also enforce error handling of non-awaited promises in statement positions.
+:::
+
+### Ignored returned generators
+
+If a generator is returned from a void function it won't even be started.
+
+
+
+
+```ts
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(function* () {
+ console.log('Hello');
+ yield;
+ console.log('World');
+});
+```
+
+
+
+
+```ts
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(() => {
+ function* gen() {
+ console.log('Hello');
+ yield;
+ console.log('World');
+ }
+ for (const _ of gen());
+});
+```
+
+
+
+
+### Accidental dead code
+
+Returning a value from a void function often is an indication of incorrect assumptions about APIs.
+Those incorrect assumptions can often lead to unnecessary code.
+
+The following `forEach` loop is a common mistake: its author likely either meant to add `console.log` or meant to use `.map` instead.
+
+
+
+
+```ts
+['Kazik', 'Zenek'].forEach(name => `Hello, ${name}!`);
+```
+
+
+
+
+```ts
+['Kazik', 'Zenek'].forEach(name => console.log(`Hello, ${name}!`));
+```
+
+
+
+
+### Void context from extended classes
+
+This rule enforces class methods which override a void method to also be void.
+
+
+
+
+```ts
+class Foo {
+ cb() {
+ console.log('foo');
+ }
+}
+
+class Bar extends Foo {
+ cb() {
+ super.cb();
+ return 'bar';
+ }
+}
+```
+
+
+
+
+```ts
+class Foo {
+ cb() {
+ console.log('foo');
+ }
+}
+
+class Bar extends Foo {
+ cb() {
+ super.cb();
+ console.log('bar');
+ }
+}
+```
+
+
+
+
+### Void context from implemented interfaces
+
+This rule enforces class methods which implement a void method to also be void.
+
+
+
+
+```ts
+interface Foo {
+ cb(): void;
+}
+
+class Bar implements Foo {
+ cb() {
+ return 'cb';
+ }
+}
+```
+
+
+
+
+```ts
+interface Foo {
+ cb(): void;
+}
+
+class Bar implements Foo {
+ cb() {
+ console.log('cb');
+ }
+}
+```
+
+
+
+
+## Options
+
+### `allowReturnAny`
+
+{/* insert option description */}
+
+Additional incorrect code when the option is **disabled**:
+
+
+
+
+```ts option='{ "allowReturnAny": false }'
+declare function fn(cb: () => void): void;
+
+fn(() => JSON.parse('{}'));
+
+fn(() => {
+ return someUntypedApi();
+});
+```
+
+
+
+
+```ts option='{ "allowReturnAny": false }'
+declare function fn(cb: () => void): void;
+
+fn(() => void JSON.parse('{}'));
+
+fn(() => {
+ someUntypedApi();
+});
+```
+
+
+
+
+## When Not To Use It
+
+Some projects are architected so that values returned from synchronous void functions are generally safe.
+If you only want to check for misused voids with asynchronous functions then you can use [`no-misused-promises`](./no-misused-promises.mdx) instead.
+
+In browser context, an unhandled promise will be reported as an error in the console.
+It's generally a good idea to also show some kind of indicator on the page that something went wrong,
+but if you are just prototyping or don't care about that, the default behavior might be acceptable.
+In such case, instead of handling the promises and `console.error`ing them anyways, you can just disable this rule.
+
+Similarly, the default behavior of crashing the process on unhandled promise rejection
+might be acceptable when developing, for example, a CLI tool.
+If your promise handlers simply call `process.exit(1)` on rejection,
+you may prefer to avoid this rule and rely on the default behavior.
+
+## Related To
+
+- [`no-misused-promises`](./no-misused-promises.mdx) - A subset of this rule which only cares about promises.
+- [`no-floating-promises`](./no-floating-promises.mdx) - Warns about unhandled promises in _statement_ positions.
+- [`no-confusing-void-expression`](./no-confusing-void-expression.mdx) - Disallows returning _void_ values.
+
+## Further Reading
+
+- [TypeScript FAQ - Void function assignability](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void)
diff --git a/packages/eslint-plugin/src/configs/eslintrc/all.ts b/packages/eslint-plugin/src/configs/eslintrc/all.ts
index 2764e4f92a74..c42fce0eb4c7 100644
--- a/packages/eslint-plugin/src/configs/eslintrc/all.ts
+++ b/packages/eslint-plugin/src/configs/eslintrc/all.ts
@@ -154,6 +154,7 @@ export = {
'no-return-await': 'off',
'@typescript-eslint/return-await': 'error',
'@typescript-eslint/strict-boolean-expressions': 'error',
+ '@typescript-eslint/strict-void-return': 'error',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/unbound-method': 'error',
diff --git a/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts b/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
index 9dd6c95c929e..22b2fa5b69b1 100644
--- a/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
@@ -66,6 +66,7 @@ export = {
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
+ '@typescript-eslint/strict-void-return': 'off',
'@typescript-eslint/switch-exhaustiveness-check': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
diff --git a/packages/eslint-plugin/src/configs/flat/all.ts b/packages/eslint-plugin/src/configs/flat/all.ts
index 37f6b8647ddc..b7b5a24970c4 100644
--- a/packages/eslint-plugin/src/configs/flat/all.ts
+++ b/packages/eslint-plugin/src/configs/flat/all.ts
@@ -168,6 +168,7 @@ export default (
'no-return-await': 'off',
'@typescript-eslint/return-await': 'error',
'@typescript-eslint/strict-boolean-expressions': 'error',
+ '@typescript-eslint/strict-void-return': 'error',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/unbound-method': 'error',
diff --git a/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts b/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
index 15c5bb0e3dcf..63e5e73d6f9b 100644
--- a/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
@@ -73,6 +73,7 @@ export default (
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
+ '@typescript-eslint/strict-void-return': 'off',
'@typescript-eslint/switch-exhaustiveness-check': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 2a82c7e18c6b..56d00109bb3c 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -125,6 +125,7 @@ import restrictTemplateExpressions from './restrict-template-expressions';
import returnAwait from './return-await';
import sortTypeConstituents from './sort-type-constituents';
import strictBooleanExpressions from './strict-boolean-expressions';
+import strictVoidReturn from './strict-void-return';
import switchExhaustivenessCheck from './switch-exhaustiveness-check';
import tripleSlashReference from './triple-slash-reference';
import typedef from './typedef';
@@ -259,6 +260,7 @@ const rules = {
'return-await': returnAwait,
'sort-type-constituents': sortTypeConstituents,
'strict-boolean-expressions': strictBooleanExpressions,
+ 'strict-void-return': strictVoidReturn,
'switch-exhaustiveness-check': switchExhaustivenessCheck,
'triple-slash-reference': tripleSlashReference,
typedef,
diff --git a/packages/eslint-plugin/src/rules/strict-void-return.ts b/packages/eslint-plugin/src/rules/strict-void-return.ts
new file mode 100644
index 000000000000..7d4e8f8168d5
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/strict-void-return.ts
@@ -0,0 +1,428 @@
+import type { TSESTree } from '@typescript-eslint/utils';
+
+import { AST_NODE_TYPES } from '@typescript-eslint/utils';
+import * as tsutils from 'ts-api-utils';
+import * as ts from 'typescript';
+
+import * as util from '../util';
+
+type Options = [
+ {
+ allowReturnAny?: boolean;
+ },
+];
+
+type MessageId = `asyncFunc` | `nonVoidFunc` | `nonVoidReturn`;
+
+export default util.createRule({
+ name: 'strict-void-return',
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'Disallow passing a value-returning function in a position accepting a void function',
+ requiresTypeChecking: true,
+ },
+ messages: {
+ asyncFunc:
+ 'Async function used in a context where a void function is expected.',
+ nonVoidFunc:
+ 'Value-returning function used in a context where a void function is expected.',
+ nonVoidReturn:
+ 'Value returned in a context where a void return is expected.',
+ },
+ schema: [
+ {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ allowReturnAny: {
+ type: 'boolean',
+ description:
+ 'Whether to allow functions returning `any` to be used in place expecting a `void` function.',
+ },
+ },
+ },
+ ],
+ },
+ defaultOptions: [
+ {
+ allowReturnAny: false,
+ },
+ ],
+
+ create(context, [options]) {
+ const sourceCode = context.sourceCode;
+ const parserServices = util.getParserServices(context);
+ const checker = parserServices.program.getTypeChecker();
+
+ return {
+ ArrayExpression: (node): void => {
+ for (const elemNode of node.elements) {
+ if (
+ elemNode != null &&
+ elemNode.type !== AST_NODE_TYPES.SpreadElement
+ ) {
+ checkExpressionNode(elemNode);
+ }
+ }
+ },
+ ArrowFunctionExpression: (node): void => {
+ if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
+ checkExpressionNode(node.body);
+ }
+ },
+ AssignmentExpression: (node): void => {
+ checkExpressionNode(node.right); // should ignore operators like `+=` or `-=` automatically
+ },
+ 'CallExpression, NewExpression': checkFunctionCallNode,
+ JSXAttribute: (node): void => {
+ if (
+ node.value?.type === AST_NODE_TYPES.JSXExpressionContainer &&
+ node.value.expression.type !== AST_NODE_TYPES.JSXEmptyExpression
+ ) {
+ checkExpressionNode(node.value.expression);
+ }
+ },
+ MethodDefinition: checkClassMethodNode,
+ ObjectExpression: (node): void => {
+ for (const propNode of node.properties) {
+ if (propNode.type !== AST_NODE_TYPES.SpreadElement) {
+ checkObjectPropertyNode(propNode);
+ }
+ }
+ },
+ PropertyDefinition: checkClassPropertyNode,
+ ReturnStatement: (node): void => {
+ if (node.argument != null) {
+ checkExpressionNode(node.argument);
+ }
+ },
+ VariableDeclarator: (node): void => {
+ if (node.init != null) {
+ checkExpressionNode(node.init);
+ }
+ },
+ };
+
+ function isVoidReturningFunctionType(type: ts.Type): boolean {
+ const returnTypes = tsutils
+ .getCallSignaturesOfType(type)
+ .flatMap(signature =>
+ tsutils.unionConstituents(signature.getReturnType()),
+ );
+ return (
+ returnTypes.length > 0 &&
+ returnTypes.every(type =>
+ tsutils.isTypeFlagSet(type, ts.TypeFlags.Void),
+ )
+ );
+ }
+
+ /**
+ * Finds errors in any expression node.
+ *
+ * Compares the type of the node against the contextual (expected) type.
+ *
+ * @returns `true` if the expected type was void function.
+ */
+ function checkExpressionNode(node: TSESTree.Expression): boolean {
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(
+ node,
+ ) as ts.Expression;
+ const expectedType = checker.getContextualType(tsNode);
+
+ if (expectedType != null && isVoidReturningFunctionType(expectedType)) {
+ reportIfNonVoidFunction(node);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds errors in function calls.
+ *
+ * When checking arguments, we also manually figure out the argument types
+ * by iterating over all the function signatures.
+ * Thanks to this, we can find arguments like `(() => void) | (() => any)`
+ * and treat them as void too.
+ * This is done to also support checking functions like `addEventListener`
+ * which have overloads where one callback returns any.
+ *
+ * Implementation mostly based on no-misused-promises,
+ * which does this to find `(() => void) | (() => NotThenable)`
+ * and report them too.
+ */
+ function checkFunctionCallNode(
+ callNode: TSESTree.CallExpression | TSESTree.NewExpression,
+ ): void {
+ const callTsNode = parserServices.esTreeNodeToTSNodeMap.get(callNode);
+
+ const funcType = checker.getTypeAtLocation(callTsNode.expression);
+ const funcSignatures = tsutils
+ .unionConstituents(funcType)
+ .flatMap(type =>
+ ts.isCallExpression(callTsNode)
+ ? type.getCallSignatures()
+ : type.getConstructSignatures(),
+ );
+
+ for (const [argIdx, argNode] of callNode.arguments.entries()) {
+ if (argNode.type === AST_NODE_TYPES.SpreadElement) {
+ continue;
+ }
+
+ // Check against the contextual type first
+ if (checkExpressionNode(argNode)) {
+ continue;
+ }
+
+ // Check against the types from all of the call signatures
+ const argExpectedReturnTypes = funcSignatures
+ .map(s => s.parameters[argIdx])
+ .filter(Boolean)
+ .map(param =>
+ checker.getTypeOfSymbolAtLocation(param, callTsNode.expression),
+ )
+ .flatMap(paramType => tsutils.unionConstituents(paramType))
+ .flatMap(paramType => paramType.getCallSignatures())
+ .map(paramSignature => paramSignature.getReturnType());
+ if (
+ // At least one return type is void
+ argExpectedReturnTypes.some(type =>
+ tsutils.isTypeFlagSet(type, ts.TypeFlags.Void),
+ ) &&
+ // The rest are nullish or any
+ argExpectedReturnTypes.every(type =>
+ tsutils.isTypeFlagSet(
+ type,
+ ts.TypeFlags.VoidLike |
+ ts.TypeFlags.Undefined |
+ ts.TypeFlags.Null |
+ ts.TypeFlags.Any |
+ ts.TypeFlags.Never,
+ ),
+ )
+ ) {
+ // We treat this argument as void even though it might be technically any.
+ reportIfNonVoidFunction(argNode);
+ }
+ }
+ }
+
+ /**
+ * Finds errors in an object property.
+ *
+ * Object properties require different logic
+ * when the property is a method shorthand.
+ */
+ function checkObjectPropertyNode(propNode: TSESTree.Property): void {
+ const valueNode = propNode.value as TSESTree.Expression;
+ const propTsNode = parserServices.esTreeNodeToTSNodeMap.get(propNode);
+
+ if (propTsNode.kind === ts.SyntaxKind.MethodDeclaration) {
+ // Object property is a method shorthand.
+
+ if (propTsNode.name.kind === ts.SyntaxKind.ComputedPropertyName) {
+ // Don't check object methods with computed name.
+ return;
+ }
+ const objTsNode = propTsNode.parent as ts.ObjectLiteralExpression;
+ const objType = checker.getContextualType(objTsNode);
+ if (objType == null) {
+ // Expected object type is unknown.
+ return;
+ }
+ const propSymbol = checker.getPropertyOfType(
+ objType,
+ propTsNode.name.text,
+ );
+ if (propSymbol == null) {
+ // Expected object type is known, but it doesn't have this method.
+ return;
+ }
+ const propExpectedType = checker.getTypeOfSymbolAtLocation(
+ propSymbol,
+ propTsNode,
+ );
+ if (isVoidReturningFunctionType(propExpectedType)) {
+ reportIfNonVoidFunction(valueNode);
+ }
+ return;
+ }
+
+ // Object property is a regular property.
+ checkExpressionNode(valueNode);
+ }
+
+ /**
+ * Finds errors in a class property.
+ *
+ * In addition to the regular check against the contextual type,
+ * we also check against the base class property (when the class extends another class)
+ * and the implemented interfaces (when the class implements an interface).
+ */
+ function checkClassPropertyNode(
+ propNode: TSESTree.PropertyDefinition,
+ ): void {
+ if (propNode.value == null) {
+ return;
+ }
+
+ // Check in comparison to the base types.
+ for (const { baseMemberType } of util.getBaseTypesOfClassMember(
+ parserServices,
+ propNode,
+ )) {
+ if (isVoidReturningFunctionType(baseMemberType)) {
+ reportIfNonVoidFunction(propNode.value);
+ return; // Report at most one error.
+ }
+ }
+
+ // Check in comparison to the contextual type.
+ checkExpressionNode(propNode.value);
+ }
+
+ /**
+ * Finds errors in a class method.
+ *
+ * We check against the base class method (when the class extends another class)
+ * and the implemented interfaces (when the class implements an interface).
+ */
+ function checkClassMethodNode(methodNode: TSESTree.MethodDefinition): void {
+ if (
+ methodNode.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression
+ ) {
+ return;
+ }
+
+ // Check in comparison to the base types.
+ for (const { baseMemberType } of util.getBaseTypesOfClassMember(
+ parserServices,
+ methodNode,
+ )) {
+ if (isVoidReturningFunctionType(baseMemberType)) {
+ reportIfNonVoidFunction(methodNode.value);
+ return; // Report at most one error.
+ }
+ }
+ }
+
+ /**
+ * Reports an error if the provided node is not allowed in a void function context.
+ */
+ function reportIfNonVoidFunction(funcNode: TSESTree.Expression): void {
+ const allowedReturnType =
+ ts.TypeFlags.Void |
+ ts.TypeFlags.Never |
+ ts.TypeFlags.Undefined |
+ (options.allowReturnAny ? ts.TypeFlags.Any : 0);
+
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(funcNode);
+ const actualType = checker.getApparentType(
+ checker.getTypeAtLocation(tsNode),
+ );
+
+ if (
+ tsutils
+ .getCallSignaturesOfType(actualType)
+ .map(signature => signature.getReturnType())
+ .flatMap(returnType => tsutils.unionConstituents(returnType))
+ .every(type => tsutils.isTypeFlagSet(type, allowedReturnType))
+ ) {
+ // The function is already void.
+ return;
+ }
+
+ if (
+ funcNode.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
+ funcNode.type !== AST_NODE_TYPES.FunctionExpression
+ ) {
+ // The provided function is not a function literal.
+ // Report a generic error.
+ return context.report({
+ node: funcNode,
+ messageId: `nonVoidFunc`,
+ });
+ }
+
+ // The provided function is a function literal.
+
+ if (funcNode.generator) {
+ // The provided function is a generator function.
+ // Generator functions are not allowed.
+ return context.report({
+ loc: util.getFunctionHeadLoc(funcNode, sourceCode),
+ messageId: `nonVoidFunc`,
+ });
+ }
+
+ if (funcNode.async) {
+ // The provided function is an async function.
+ // Async functions aren't allowed.
+ return context.report({
+ loc: util.getFunctionHeadLoc(funcNode, sourceCode),
+ messageId: `asyncFunc`,
+ });
+ }
+
+ if (funcNode.body.type !== AST_NODE_TYPES.BlockStatement) {
+ // The provided function is an arrow function shorthand without braces.
+ return context.report({
+ node: funcNode.body,
+ messageId: `nonVoidReturn`,
+ });
+ }
+
+ // The function is a regular or arrow function with a block body.
+
+ // Check return type annotation.
+ if (funcNode.returnType != null) {
+ // The provided function has an explicit return type annotation.
+ const typeAnnotationNode = funcNode.returnType.typeAnnotation;
+ if (typeAnnotationNode.type !== AST_NODE_TYPES.TSVoidKeyword) {
+ // The explicit return type is not `void`.
+ return context.report({
+ node: typeAnnotationNode,
+ messageId: `nonVoidFunc`,
+ });
+ }
+ }
+
+ // Iterate over all function's return statements.
+ for (const statement of util.walkStatements(funcNode.body.body)) {
+ if (
+ statement.type !== AST_NODE_TYPES.ReturnStatement ||
+ statement.argument == null
+ ) {
+ // We only care about return statements with a value.
+ continue;
+ }
+
+ const returnType = checker.getTypeAtLocation(
+ parserServices.esTreeNodeToTSNodeMap.get(statement.argument),
+ );
+ if (tsutils.isTypeFlagSet(returnType, allowedReturnType)) {
+ // Only visit return statements with invalid type.
+ continue;
+ }
+
+ // This return statement causes the non-void return type.
+ const returnKeyword = util.nullThrows(
+ sourceCode.getFirstToken(statement, {
+ filter: token => token.value === 'return',
+ }),
+ util.NullThrowsReasons.MissingToken('return keyword', statement.type),
+ );
+ context.report({
+ node: returnKeyword,
+ messageId: `nonVoidReturn`,
+ });
+ }
+
+ // No invalid returns found. The function is allowed.
+ }
+ },
+});
diff --git a/packages/eslint-plugin/src/util/getBaseTypesOfClassMember.ts b/packages/eslint-plugin/src/util/getBaseTypesOfClassMember.ts
new file mode 100644
index 000000000000..def2ec1622e7
--- /dev/null
+++ b/packages/eslint-plugin/src/util/getBaseTypesOfClassMember.ts
@@ -0,0 +1,47 @@
+import type {
+ TSESTree,
+ ParserServicesWithTypeInformation,
+} from '@typescript-eslint/utils';
+import type * as ts from 'typescript';
+
+/**
+ * Given a member of a class which extends another class or implements an interface,
+ * yields the corresponding member type for each of the base class/interfaces.
+ */
+export function* getBaseTypesOfClassMember(
+ services: ParserServicesWithTypeInformation,
+ memberNode: TSESTree.MethodDefinition | TSESTree.PropertyDefinition,
+): Generator<{
+ baseType: ts.Type;
+ baseMemberType: ts.Type;
+ heritageToken: ts.SyntaxKind.ExtendsKeyword | ts.SyntaxKind.ImplementsKeyword;
+}> {
+ const memberTsNode = services.esTreeNodeToTSNodeMap.get(memberNode);
+ if (memberTsNode.name == null) {
+ return;
+ }
+ const checker = services.program.getTypeChecker();
+ const memberSymbol = checker.getSymbolAtLocation(memberTsNode.name);
+ if (memberSymbol == null) {
+ return;
+ }
+ const classNode = memberTsNode.parent as ts.ClassLikeDeclaration;
+ for (const clauseNode of classNode.heritageClauses ?? []) {
+ for (const baseTypeNode of clauseNode.types) {
+ const baseType = checker.getTypeAtLocation(baseTypeNode);
+ const baseMemberSymbol = checker.getPropertyOfType(
+ baseType,
+ memberSymbol.name,
+ );
+ if (baseMemberSymbol == null) {
+ continue;
+ }
+ const baseMemberType = checker.getTypeOfSymbolAtLocation(
+ baseMemberSymbol,
+ memberTsNode,
+ );
+ const heritageToken = clauseNode.token;
+ yield { baseMemberType, baseType, heritageToken };
+ }
+ }
+}
diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts
index 035fca0cec4a..1eb78e3f885c 100644
--- a/packages/eslint-plugin/src/util/index.ts
+++ b/packages/eslint-plugin/src/util/index.ts
@@ -3,6 +3,7 @@ import { ESLintUtils } from '@typescript-eslint/utils';
export * from './astUtils';
export * from './collectUnusedVariables';
export * from './createRule';
+export * from './getBaseTypesOfClassMember';
export * from './getFixOrSuggest';
export * from './getFunctionHeadLoc';
export * from './getOperatorPrecedence';
@@ -29,6 +30,7 @@ export * from './getValueOfLiteralType';
export * from './isHigherPrecedenceThanAwait';
export * from './skipChainExpression';
export * from './truthinessUtils';
+export * from './walkStatements';
// this is done for convenience - saves migrating all of the old rules
export * from '@typescript-eslint/type-utils';
diff --git a/packages/eslint-plugin/src/util/walkStatements.ts b/packages/eslint-plugin/src/util/walkStatements.ts
new file mode 100644
index 000000000000..8bc80153a75c
--- /dev/null
+++ b/packages/eslint-plugin/src/util/walkStatements.ts
@@ -0,0 +1,58 @@
+import type { TSESTree } from '@typescript-eslint/utils';
+
+import { AST_NODE_TYPES } from '@typescript-eslint/utils';
+
+/**
+ * Yields all statement nodes in a block, including nested blocks.
+ *
+ * You can use it to find all return statements in a function body.
+ */
+export function* walkStatements(
+ body: readonly TSESTree.Statement[],
+): Generator {
+ for (const statement of body) {
+ switch (statement.type) {
+ case AST_NODE_TYPES.BlockStatement: {
+ yield* walkStatements(statement.body);
+ continue;
+ }
+ case AST_NODE_TYPES.SwitchStatement: {
+ for (const switchCase of statement.cases) {
+ yield* walkStatements(switchCase.consequent);
+ }
+ continue;
+ }
+ case AST_NODE_TYPES.IfStatement: {
+ yield* walkStatements([statement.consequent]);
+ if (statement.alternate) {
+ yield* walkStatements([statement.alternate]);
+ }
+ continue;
+ }
+ case AST_NODE_TYPES.WhileStatement:
+ case AST_NODE_TYPES.DoWhileStatement:
+ case AST_NODE_TYPES.ForStatement:
+ case AST_NODE_TYPES.ForInStatement:
+ case AST_NODE_TYPES.ForOfStatement:
+ case AST_NODE_TYPES.WithStatement:
+ case AST_NODE_TYPES.LabeledStatement: {
+ yield* walkStatements([statement.body]);
+ continue;
+ }
+ case AST_NODE_TYPES.TryStatement: {
+ yield* walkStatements([statement.block]);
+ if (statement.handler) {
+ yield* walkStatements([statement.handler.body]);
+ }
+ if (statement.finalizer) {
+ yield* walkStatements([statement.finalizer]);
+ }
+ continue;
+ }
+ default: {
+ yield statement;
+ continue;
+ }
+ }
+ }
+}
diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/strict-void-return.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/strict-void-return.shot
new file mode 100644
index 000000000000..ed147791a399
--- /dev/null
+++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/strict-void-return.shot
@@ -0,0 +1,213 @@
+Incorrect
+
+const getNothing: () => void = () => 2137;
+ ~~~~ Value returned in a context where a void return is expected.
+const getString: () => string = () => 'Hello';
+const maybeString = Math.random() > 0.1 ? getNothing() : getString();
+if (maybeString) console.log(maybeString.toUpperCase()); // ❌ Crash if getNothing was called
+
+Correct
+
+const getNothing: () => void = () => {};
+const getString: () => string = () => 'Hello';
+const maybeString = Math.random() > 0.1 ? getNothing() : getString();
+if (maybeString) console.log(maybeString.toUpperCase()); // ✅ No crash
+
+Incorrect
+
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(async () => {
+ ~~ Async function used in a context where a void function is expected.
+ const response = await fetch('https://api.example.com/');
+ const data = await response.json();
+ console.log(data);
+});
+
+Correct
+
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(() => {
+ (async () => {
+ const response = await fetch('https://api.example.com/');
+ const data = await response.json();
+ console.log(data);
+ })().catch(console.error);
+});
+
+Incorrect
+
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(function* () {
+ ~~~~~~~~~~ Value-returning function used in a context where a void function is expected.
+ console.log('Hello');
+ yield;
+ console.log('World');
+});
+
+Correct
+
+declare function takesCallback(cb: () => void): void;
+
+takesCallback(() => {
+ function* gen() {
+ console.log('Hello');
+ yield;
+ console.log('World');
+ }
+ for (const _ of gen());
+});
+
+Incorrect
+
+['Kazik', 'Zenek'].forEach(name => `Hello, ${name}!`);
+ ~~~~~~~~~~~~~~~~~ Value returned in a context where a void return is expected.
+
+Correct
+
+['Kazik', 'Zenek'].forEach(name => console.log(`Hello, ${name}!`));
+
+Incorrect
+
+declare function takesCallback(cb: () => void): void;
+declare function TakesCallback(props: { cb: () => void }): string;
+declare let callback: () => void;
+declare let returnsCallback: () => () => void;
+declare let callbackObj: Record void>;
+declare let callbackArr: (() => void)[];
+
+takesCallback(() => 'Hello');
+ ~~~~~~~ Value returned in a context where a void return is expected.
+ 'Hello'} />;
+ ~~~~~~~ Value returned in a context where a void return is expected.
+callback = () => 'Hello';
+ ~~~~~~~ Value returned in a context where a void return is expected.
+returnsCallback = () => {
+ return () => 'Hello';
+ ~~~~~~~ Value returned in a context where a void return is expected.
+};
+callbackObj = {
+ hello: () => 'Hello',
+ ~~~~~~~ Value returned in a context where a void return is expected.
+};
+callbackArr = [() => 'Hello'];
+ ~~~~~~~ Value returned in a context where a void return is expected.
+
+Correct
+
+declare function takesCallback(cb: () => void): void;
+declare function TakesCallback(props: { cb: () => void }): string;
+declare let callback: () => void;
+declare let returnsCallback: () => () => void;
+declare let callbackObj: Record void>;
+declare let callbackArr: (() => void)[];
+
+takesCallback(() => console.log('Hello'));
+ console.log('Hello')} />;
+callback = () => console.log('Hello');
+returnsCallback = () => {
+ return () => console.log('Hello');
+};
+callbackObj = {
+ hello: () => console.log('Hello'),
+};
+callbackArr = [() => console.log('Hello')];
+
+Incorrect
+
+///
+
+document.addEventListener('click', () => {
+ return 'Clicked';
+ ~~~~~~ Value returned in a context where a void return is expected.
+});
+
+Correct
+
+///
+
+document.addEventListener('click', () => {
+ console.log('Clicked');
+});
+
+Incorrect
+
+class Foo {
+ cb() {
+ console.log('foo');
+ }
+}
+
+class Bar extends Foo {
+ cb() {
+ super.cb();
+ return 'bar';
+ ~~~~~~ Value returned in a context where a void return is expected.
+ }
+}
+
+Correct
+
+class Foo {
+ cb() {
+ console.log('foo');
+ }
+}
+
+class Bar extends Foo {
+ cb() {
+ super.cb();
+ console.log('bar');
+ }
+}
+
+Incorrect
+
+interface Foo {
+ cb(): void;
+}
+
+class Bar implements Foo {
+ cb() {
+ return 'cb';
+ ~~~~~~ Value returned in a context where a void return is expected.
+ }
+}
+
+Correct
+
+interface Foo {
+ cb(): void;
+}
+
+class Bar implements Foo {
+ cb() {
+ console.log('cb');
+ }
+}
+
+Incorrect
+Options: { "allowReturnAny": false }
+
+declare function fn(cb: () => void): void;
+
+fn(() => JSON.parse('{}'));
+ ~~~~~~~~~~~~~~~~ Value returned in a context where a void return is expected.
+
+fn(() => {
+ return someUntypedApi();
+ ~~~~~~ Value returned in a context where a void return is expected.
+});
+
+Correct
+Options: { "allowReturnAny": false }
+
+declare function fn(cb: () => void): void;
+
+fn(() => void JSON.parse('{}'));
+
+fn(() => {
+ someUntypedApi();
+});
diff --git a/packages/eslint-plugin/tests/rules/strict-void-return.test.ts b/packages/eslint-plugin/tests/rules/strict-void-return.test.ts
new file mode 100644
index 000000000000..01d6fcf16943
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/strict-void-return.test.ts
@@ -0,0 +1,2740 @@
+import { noFormat, RuleTester } from '@typescript-eslint/rule-tester';
+
+import rule from '../../src/rules/strict-void-return';
+import { getFixturesRootDir } from '../RuleTester';
+
+const rootDir = getFixturesRootDir();
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: rootDir,
+ },
+ },
+});
+
+ruleTester.run('strict-void-return', rule, {
+ valid: [
+ {
+ code: `
+ declare function foo(cb: {}): void;
+ foo(() => () => []);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ type Void = void;
+ foo((): Void => {
+ return;
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo((): ReturnType => {
+ return;
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: any): void;
+ foo(() => () => []);
+ `,
+ },
+ {
+ code: `
+ declare class Foo {
+ constructor(cb: unknown): void;
+ }
+ new Foo(() => ({}));
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => {}): void;
+ foo(() => 1 as any);
+ `,
+ options: [{ allowReturnAny: true }],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => {
+ throw new Error('boom');
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ declare function boom(): never;
+ foo(() => boom());
+ foo(boom);
+ `,
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => any): void;
+ };
+ new Foo(function () {
+ return 1;
+ });
+ `,
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => unknown): void;
+ };
+ new Foo(function () {
+ return 1;
+ });
+ `,
+ },
+ {
+ code: `
+ declare const foo: {
+ bar(cb1: () => unknown, cb2: () => void): void;
+ };
+ foo.bar(
+ function () {
+ return 1;
+ },
+ function () {
+ return;
+ },
+ );
+ `,
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => string | void): void;
+ };
+ new Foo(() => {
+ if (maybe) {
+ return 'a';
+ } else {
+ return 'b';
+ }
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo void>(cb: Cb): void;
+ foo(() => {
+ console.log('a');
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: (() => void) | (() => string)): void;
+ foo(() => {
+ label: while (maybe) {
+ for (let i = 0; i < 10; i++) {
+ switch (i) {
+ case 0:
+ continue;
+ case 1:
+ return 'a';
+ }
+ }
+ }
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: (() => void) | null): void;
+ foo(null);
+ `,
+ },
+ {
+ code: `
+ interface Cb {
+ (): void;
+ (): string;
+ }
+ declare const Foo: {
+ new (cb: Cb): void;
+ };
+ new Foo(() => {
+ do {
+ try {
+ throw 1;
+ } catch {
+ return 'a';
+ }
+ } while (maybe);
+ });
+ `,
+ },
+ {
+ code: `
+ declare const foo: ((cb: () => boolean) => void) | ((cb: () => void) => void);
+ foo(() => false);
+ `,
+ },
+ {
+ code: `
+ declare const foo: {
+ (cb: () => boolean): void;
+ (cb: () => void): void;
+ };
+ foo(function () {
+ with ({}) {
+ return false;
+ }
+ });
+ `,
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => void): void;
+ (cb: () => unknown): void;
+ };
+ Foo(() => false);
+ `,
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => any): void;
+ (cb: () => void): void;
+ };
+ new Foo(() => false);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => boolean): void;
+ declare function foo(cb: () => void): void;
+ foo(() => false);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => Promise): void;
+ declare function foo(cb: () => void): void;
+ foo(async () => {});
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => 1 as any);
+ `,
+ options: [{ allowReturnAny: true }],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => {});
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ const cb = () => {};
+ foo(cb);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(function () {});
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(cb);
+ function cb() {}
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => undefined);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(function () {
+ return;
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(function () {
+ return void 0;
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => {
+ return;
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ declare function cb(): never;
+ foo(cb);
+ `,
+ },
+ {
+ code: `
+ declare class Foo {
+ constructor(cb: () => void): any;
+ }
+ declare function cb(): void;
+ new Foo(cb);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(cb);
+ function cb() {
+ throw new Error('boom');
+ }
+ `,
+ },
+ {
+ code: `
+ declare function foo(arg: string, cb: () => void): void;
+ declare function cb(): undefined;
+ foo('arg', cb);
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb?: () => void): void;
+ foo();
+ `,
+ },
+ {
+ code: `
+ declare class Foo {
+ constructor(cb?: () => void): void;
+ }
+ declare function cb(): void;
+ new Foo(cb);
+ `,
+ },
+ {
+ code: `
+ declare function foo(...cbs: Array<() => void>): void;
+ foo(
+ () => {},
+ () => void null,
+ () => undefined,
+ );
+ `,
+ },
+ {
+ code: `
+ declare function foo(...cbs: Array<() => void>): void;
+ declare const cbs: Array<() => void>;
+ foo(...cbs);
+ `,
+ },
+ {
+ code: `
+ declare function foo(...cbs: [() => any, () => void, (() => void)?]): void;
+ foo(
+ async () => {},
+ () => void null,
+ () => undefined,
+ );
+ `,
+ },
+ {
+ code: `
+ let cb;
+ cb = async () => 10;
+ `,
+ },
+ {
+ code: `
+ const foo: () => void = () => {};
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ const foo: () => void = cb;
+ `,
+ },
+ {
+ code: `
+ const foo: () => void = function () {
+ throw new Error('boom');
+ };
+ `,
+ },
+ {
+ code: `
+ const foo: { (): string; (): void } = () => {
+ return 'a';
+ };
+ `,
+ },
+ {
+ code: `
+ const foo: (() => void) | (() => number) = () => {
+ return 1;
+ };
+ `,
+ },
+ {
+ code: `
+ type Foo = () => void;
+ const foo: Foo = cb;
+ function cb() {
+ return void null;
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo {
+ (): void;
+ }
+ const foo: Foo = cb;
+ function cb() {
+ return undefined;
+ }
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ declare let foo: () => void;
+ foo = cb;
+ `,
+ },
+ {
+ code: `
+ declare let foo: () => void;
+ foo += () => 1;
+ `,
+ },
+ {
+ code: `
+ declare function defaultCb(): object;
+ declare let foo: { cb?: () => void };
+ // default doesn't have to be void
+ const { cb = defaultCb } = foo;
+ `,
+ },
+ {
+ code: `
+ let foo: (() => void) | null = null;
+ foo &&= null;
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ let foo: (() => void) | boolean = false;
+ foo ||= cb;
+ `,
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ return {}} />;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ return ;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ return ;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ return {}} /> />;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = () => void;
+ declare function Foo(props: { cb: Cb; s: string }): unknown;
+ return ;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = () => void;
+ declare function Foo(props: { x: number; cb?: Cb }): unknown;
+ return ;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = (() => void) | (() => number);
+ declare function Foo(props: { cb?: Cb }): unknown;
+ return (
+
+ );
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ interface Props {
+ cb: ((arg: unknown) => void) | boolean;
+ }
+ declare function Foo(props: Props): unknown;
+ return ;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ interface Props {
+ cb: (() => void) | (() => Promise);
+ }
+ declare function Foo(props: Props): any;
+ const _ = {}} />;
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ interface Props {
+ children: (arg: unknown) => void;
+ }
+ declare function Foo(props: Props): unknown;
+ declare function cb(): void;
+ return {cb};
+ `,
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ declare function foo(cbs: { arg: number; cb: () => void }): void;
+ foo({ arg: 1, cb: () => undefined });
+ `,
+ },
+ {
+ code: `
+ declare let foo: { arg?: string; cb: () => void };
+ foo = {
+ cb: () => {
+ return something;
+ },
+ };
+ `,
+ options: [{ allowReturnAny: true }],
+ },
+ {
+ code: `
+ declare let foo: { cb: () => void };
+ foo = {
+ cb() {
+ return something;
+ },
+ };
+ `,
+ options: [{ allowReturnAny: true }],
+ },
+ {
+ code: `
+ declare let foo: { cb: () => void };
+ foo = {
+ // don't check this thing
+ cb = () => 1,
+ };
+ `,
+ },
+ {
+ code: `
+ declare let foo: { cb: (n: number) => void };
+ let method = 'cb';
+ foo = {
+ // don't check computed methods
+ [method](n) {
+ return n;
+ },
+ };
+ `,
+ },
+ {
+ code: `
+ // no contextual type for object
+ let foo = {
+ cb(n) {
+ return n;
+ },
+ };
+ `,
+ },
+ {
+ code: `
+ interface Foo {
+ fn(): void;
+ }
+ // no symbol for method cb
+ let foo: Foo = {
+ cb(n) {
+ return n;
+ },
+ };
+ `,
+ },
+ {
+ code: `
+ declare let foo: { cb: (() => void) | number };
+ foo = {
+ cb: 0,
+ };
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ const foo: Record void> = {
+ cb1: cb,
+ cb2: cb,
+ };
+ `,
+ },
+ {
+ code: `
+ declare function cb(): string;
+ const foo: Record void> = {
+ ...cb,
+ };
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ const foo: Array<(() => void) | false> = [false, cb, () => cb()];
+ `,
+ },
+ {
+ code: `
+ declare function cb(): void;
+ const foo: [string, () => void, (() => void)?] = ['asd', cb];
+ `,
+ },
+ {
+ code: `
+ const foo: { cbs: Array<() => void> | null } = {
+ cbs: [
+ function () {
+ return undefined;
+ },
+ () => {
+ return void 0;
+ },
+ null,
+ ],
+ };
+ `,
+ },
+ {
+ code: `
+ const foo: { cb: () => void } = class {
+ static cb = () => {};
+ };
+ `,
+ },
+ {
+ code: `
+ class Foo {
+ foo;
+ }
+ `,
+ },
+ {
+ code: `
+ class Bar {
+ foo() {}
+ }
+ class Foo extends Bar {
+ foo();
+ }
+ `,
+ },
+ {
+ code: `
+ interface Bar {
+ foo(): void;
+ }
+ class Foo implements Bar {
+ get foo() {
+ return new Date();
+ }
+ set foo() {
+ return new Date('wtf');
+ }
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo {
+ foo: () => void = () => undefined;
+ }
+ `,
+ },
+ {
+ code: `
+ class Bar {}
+ class Foo extends Bar {
+ foo = () => 1;
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo extends Wtf {
+ foo = () => 1;
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo extends Wtf {
+ [unknown] = () => 1;
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo {
+ cb = () => {
+ console.log('siema');
+ };
+ }
+ class Bar extends Foo {
+ cb = () => {
+ console.log('nara');
+ };
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo {
+ cb1 = () => {};
+ }
+ class Bar extends Foo {
+ cb2() {}
+ }
+ class Baz extends Bar {
+ cb1 = () => {
+ console.log('siema');
+ };
+ cb2() {
+ console.log('nara');
+ }
+ }
+ `,
+ },
+ {
+ code: `
+ class Foo {
+ fn() {
+ return 'a';
+ }
+ cb() {}
+ }
+ void class extends Foo {
+ cb() {
+ if (maybe) {
+ console.log('siema');
+ } else {
+ console.log('nara');
+ }
+ }
+ };
+ `,
+ },
+ {
+ code: `
+ abstract class Foo {
+ abstract cb(): void;
+ }
+ class Bar extends Foo {
+ cb() {
+ console.log('a');
+ }
+ }
+ `,
+ },
+ {
+ code: `
+ class Bar implements Foo {
+ cb = () => 1;
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo {
+ cb: () => void;
+ }
+ class Bar implements Foo {
+ cb = () => {};
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo {
+ cb: () => void;
+ }
+ class Bar implements Foo {
+ get cb() {
+ return () => {};
+ }
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo {
+ cb(): void;
+ }
+ class Bar implements Foo {
+ cb() {
+ return undefined;
+ }
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo1 {
+ cb1(): void;
+ }
+ interface Foo2 {
+ cb2: () => void;
+ }
+ class Bar implements Foo1, Foo2 {
+ cb1() {}
+ cb2() {}
+ }
+ `,
+ },
+ {
+ code: `
+ interface Foo1 {
+ cb1(): void;
+ }
+ interface Foo2 extends Foo1 {
+ cb2: () => void;
+ }
+ class Bar implements Foo2 {
+ cb1() {}
+ cb2() {}
+ }
+ `,
+ },
+ {
+ code: `
+ declare let foo: () => () => void;
+ foo = () => () => {};
+ `,
+ },
+ {
+ code: `
+ declare let foo: { f(): () => void };
+ foo = {
+ f() {
+ return () => undefined;
+ },
+ };
+ function cb() {}
+ `,
+ },
+ {
+ code: `
+ declare let foo: { f(): () => void };
+ foo.f = function () {
+ return () => {};
+ };
+ `,
+ },
+ {
+ code: `
+ declare let foo: () => (() => void) | string;
+ foo = () => 'asd' + 'zxc';
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: () => () => void): void;
+ foo(function () {
+ return () => {};
+ });
+ `,
+ },
+ {
+ code: `
+ declare function foo(cb: (arg: string) => () => void): void;
+ declare function foo(cb: (arg: number) => () => boolean): void;
+ foo((arg: number) => {
+ return cb;
+ });
+ function cb() {
+ return true;
+ }
+ `,
+ },
+ ],
+ invalid: [
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => null);
+ `,
+ errors: [
+ {
+ column: 19,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: noFormat`
+ declare function foo(cb: () => void): void;
+ foo(() => (((true))));
+ `,
+ errors: [
+ {
+ column: 22,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: noFormat`
+ declare function foo(cb: () => void): void;
+ foo(() => {
+ if (maybe) {
+ return (((1) + 1));
+ }
+ });
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(arg: number, cb: () => void): void;
+ foo(0, () => 0);
+ `,
+ errors: [
+ {
+ column: 22,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb?: { (): void }): void;
+ foo(() => () => {});
+ `,
+ errors: [
+ {
+ column: 19,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const obj: { foo(cb: () => void) } | null;
+ obj?.foo(() => JSON.parse('{}'));
+ `,
+ errors: [
+ {
+ column: 24,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ ((cb: () => void) => cb())!(() => 1);
+ `,
+ errors: [
+ {
+ column: 43,
+ line: 2,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: { (): void }): void;
+ declare function cb(): string;
+ foo(cb);
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ type AnyFunc = (...args: unknown[]) => unknown;
+ declare function foo(cb: F): void;
+ foo(async () => ({}));
+ foo<() => void>(async () => ({}));
+ `,
+ errors: [
+ {
+ column: 34,
+ line: 5,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ function foo(arg: T, cb: () => T);
+ function foo(arg: null, cb: () => void);
+ function foo(arg: any, cb: () => any) {}
+
+ foo(null, () => Math.random());
+ `,
+ errors: [
+ {
+ column: 25,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(arg: T, cb: () => T): void;
+ declare function foo(arg: any, cb: () => void): void;
+
+ foo(null, async () => {});
+ `,
+ errors: [
+ {
+ column: 28,
+ line: 5,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ declare function foo(cb: () => any): void;
+ foo(async () => {
+ return Math.random();
+ });
+ `,
+ errors: [
+ {
+ column: 22,
+ line: 4,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: { (): void }): void;
+ foo(cb);
+ async function cb() {}
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 3,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo void>(cb: Cb): void;
+ foo(() => {
+ console.log('a');
+ return 1;
+ });
+ `,
+ errors: [
+ {
+ column: 11,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ function bar number>(cb: Cb) {
+ foo(cb);
+ }
+ `,
+ errors: [
+ {
+ column: 15,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: { (): void }): void;
+ const cb = () => dunno;
+ foo!(cb);
+ `,
+ errors: [
+ {
+ column: 14,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const foo: {
+ (arg: boolean, cb: () => void): void;
+ };
+ foo(false, () => Promise.resolve(undefined));
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const foo: {
+ bar(cb1: () => any, cb2: () => void): void;
+ };
+ foo.bar(
+ () => Promise.resolve(1),
+ () => Promise.resolve(1),
+ );
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 7,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => void): void;
+ };
+ new Foo(async () => {});
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 5,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => {
+ label: while (maybe) {
+ for (const i of [1, 2, 3]) {
+ if (maybe) return null;
+ else return null;
+ }
+ }
+ return void 0;
+ });
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 20,
+ line: 7,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(() => {
+ do {
+ try {
+ throw 1;
+ } catch (e) {
+ return null;
+ } finally {
+ console.log('finally');
+ }
+ } while (maybe);
+ });
+ `,
+ errors: [
+ {
+ column: 15,
+ line: 8,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: () => void): void;
+ foo(async () => {
+ try {
+ await Promise.resolve();
+ } catch {
+ console.error('fail');
+ }
+ });
+ `,
+ errors: [
+ {
+ column: 22,
+ line: 3,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => void): void;
+ (cb: () => unknown): void;
+ };
+ new Foo(() => false);
+ `,
+ errors: [
+ {
+ column: 23,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare const Foo: {
+ new (cb: () => any): void;
+ (cb: () => void): void;
+ };
+ Foo(() => false);
+ `,
+ errors: [
+ {
+ column: 19,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ interface Cb {
+ (arg: string): void;
+ (arg: number): void;
+ }
+ declare function foo(cb: Cb): void;
+ foo(cb);
+ function cb() {
+ return true;
+ }
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 7,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(
+ cb: ((arg: number) => void) | ((arg: string) => void),
+ ): void;
+ foo(cb);
+ function cb() {
+ return 1 + 1;
+ }
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 5,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(cb: (() => void) | null): void;
+ declare function cb(): boolean;
+ foo(cb);
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(...cbs: Array<() => void>): void;
+ foo(
+ () => {},
+ () => false,
+ () => 0,
+ () => '',
+ );
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 17,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 17,
+ line: 7,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(...cbs: [() => void, () => void, (() => void)?]): void;
+ foo(
+ () => {},
+ () => Math.random(),
+ () => (1).toString(),
+ );
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 17,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ interface Ev {}
+ interface EvMap {
+ DOMContentLoaded: Ev;
+ }
+ type EvListOrEvListObj = EvList | EvListObj;
+ interface EvList {
+ (evt: Event): void;
+ }
+ interface EvListObj {
+ handleEvent(object: Ev): void;
+ }
+ interface Win {
+ addEventListener(
+ type: K,
+ listener: (ev: EvMap[K]) => any,
+ ): void;
+ addEventListener(type: string, listener: EvListOrEvListObj): void;
+ }
+ declare const win: Win;
+ win.addEventListener('DOMContentLoaded', ev => ev);
+ win.addEventListener('custom', ev => ev);
+ `,
+ errors: [
+ {
+ column: 56,
+ line: 21,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 46,
+ line: 22,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function foo(x: null, cb: () => void): void;
+ declare function foo(x: unknown, cb: () => any): void;
+ foo({}, async () => {});
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 4,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const arr = [1, 2];
+ arr.forEach(async x => {
+ console.log(x);
+ });
+ `,
+ errors: [
+ {
+ column: 29,
+ line: 3,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ [1, 2].forEach(async x => console.log(x));
+ `,
+ errors: [
+ {
+ column: 32,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = () => false;
+ `,
+ errors: [
+ {
+ column: 39,
+ line: 2,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const { name }: () => void = function foo() {
+ return false;
+ };
+ `,
+ errors: [{ column: 11, line: 3, messageId: 'nonVoidReturn' }],
+ },
+ {
+ code: `
+ declare const foo: Record void>;
+ foo['a' + 'b'] = () => true;
+ `,
+ errors: [{ column: 32, line: 3, messageId: 'nonVoidReturn' }],
+ },
+ {
+ code: `
+ const foo: () => void = async () => Promise.resolve(true);
+ `,
+ errors: [
+ {
+ column: 42,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: 'const cb: () => void = (): Array => [];',
+ errors: [
+ {
+ column: 45,
+ line: 1,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const cb: () => void = (): Array => {
+ return [];
+ };
+ `,
+ errors: [
+ {
+ column: 36,
+ line: 2,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: noFormat`const cb: () => void = function*foo() {}`,
+ errors: [
+ {
+ column: 24,
+ line: 1,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: 'const cb: () => void = (): Promise => Promise.resolve(1);',
+ errors: [
+ {
+ column: 47,
+ line: 1,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const cb: () => void = async (): Promise => {
+ try {
+ return Promise.resolve(1);
+ } catch {}
+ };
+ `,
+ errors: [
+ {
+ column: 58,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: 'const cb: () => void = async (): Promise => Promise.resolve(1);',
+ errors: [
+ {
+ column: 50,
+ line: 1,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = async () => {
+ try {
+ return 1;
+ } catch {}
+ };
+ `,
+ errors: [
+ {
+ column: 42,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = async (): Promise => {
+ try {
+ await Promise.resolve();
+ } finally {
+ }
+ };
+ `,
+ errors: [
+ {
+ column: 57,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = async () => {
+ try {
+ await Promise.resolve();
+ } catch (err) {
+ console.error(err);
+ }
+ console.log('ok');
+ };
+ `,
+ errors: [
+ {
+ column: 42,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: 'const foo: () => void = (): number => {};',
+ errors: [
+ {
+ column: 29,
+ line: 1,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function cb(): boolean;
+ const foo: () => void = cb;
+ `,
+ errors: [
+ {
+ column: 33,
+ line: 3,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = function () {
+ if (maybe) {
+ return null;
+ } else {
+ return null;
+ }
+ };
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 4,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 13,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: () => void = function () {
+ if (maybe) {
+ console.log('elo');
+ return { [1]: Math.random() };
+ }
+ };
+ `,
+ errors: [
+ {
+ column: 13,
+ line: 5,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: { (arg: number): void; (arg: string): void } = arg => {
+ console.log('foo');
+ switch (typeof arg) {
+ case 'number':
+ return 0;
+ case 'string':
+ return '';
+ }
+ };
+ `,
+ errors: [
+ {
+ column: 15,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 15,
+ line: 8,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ const foo: ((arg: number) => void) | ((arg: string) => void) = async () => {
+ return 1;
+ };
+ `,
+ errors: [
+ {
+ column: 81,
+ line: 2,
+ messageId: 'asyncFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ type Foo = () => void;
+ const foo: Foo = cb;
+ function cb() {
+ return [1, 2, 3];
+ }
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 3,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ interface Foo {
+ (): void;
+ }
+ const foo: Foo = cb;
+ function cb() {
+ return { a: 1 };
+ }
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 5,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function cb(): unknown;
+ declare let foo: () => void;
+ foo = cb;
+ `,
+ errors: [
+ {
+ column: 15,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare let foo: { arg?: string; cb?: () => void };
+ foo.cb = () => {
+ return 'siema';
+ console.log('siema');
+ };
+ `,
+ errors: [
+ {
+ column: 11,
+ line: 4,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function cb(): unknown;
+ let foo: (() => void) | null = null;
+ foo ??= cb;
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function cb(): unknown;
+ let foo: (() => void) | boolean = false;
+ foo ||= cb;
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function cb(): unknown;
+ let foo: (() => void) | boolean = false;
+ foo &&= cb;
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ return 1} />;
+ `,
+ errors: [
+ {
+ column: 31,
+ line: 3,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ declare function Foo(props: { cb: () => void }): unknown;
+ declare function getNull(): null;
+ return (
+ {
+ if (maybe) return Math.random();
+ else return getNull();
+ }}
+ />
+ );
+ `,
+ errors: [
+ {
+ column: 26,
+ line: 7,
+ messageId: 'nonVoidReturn',
+ },
+ {
+ column: 20,
+ line: 8,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = () => void;
+ declare function Foo(props: { cb: Cb; s: string }): unknown;
+ return ;
+ `,
+ errors: [
+ {
+ column: 25,
+ line: 4,
+ messageId: 'asyncFunc',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = () => void;
+ declare function Foo(props: { n: number; cb?: Cb }): unknown;
+ return ;
+ `,
+ errors: [
+ {
+ column: 34,
+ line: 4,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type Cb = ((arg: string) => void) | ((arg: number) => void);
+ declare function Foo(props: { cb?: Cb }): unknown;
+ return (
+
+ );
+ `,
+ errors: [
+ {
+ column: 17,
+ line: 6,
+ messageId: 'nonVoidFunc',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ interface Props {
+ cb: ((arg: unknown) => void) | boolean;
+ }
+ declare function Foo(props: Props): unknown;
+ return x} />;
+ `,
+ errors: [
+ {
+ column: 30,
+ line: 6,
+ messageId: 'nonVoidReturn',
+ },
+ ],
+ filename: 'react.tsx',
+ },
+ {
+ code: `
+ type EventHandler = { bivarianceHack(event: E): void }['bivarianceHack'];
+ interface ButtonProps {
+ onClick?: EventHandler | undefined;
+ }
+ declare function Button(props: ButtonProps): unknown;
+ function App() {
+ return