strict-void-return
Disallow passing a value-returning function in a position accepting a void function.
This rule requires type information to run, which comes with performance tradeoffs.
- Flat Config
- Legacy Config
export default tseslint.config({
rules: {
"@typescript-eslint/strict-void-return": "error"
}
});
module.exports = {
"rules": {
"@typescript-eslint/strict-void-return": "error"
}
};
Try this rule in the playground ↗
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-returning 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.
// 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:
- ❌ Incorrect
- ✅ Correct
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
Open in Playgroundconst getNothing: () => void = () => {};
const getString: () => string = () => 'Hello';
const maybeString = Math.random() > 0.1 ? getNothing() : getString();
if (maybeString) console.log(maybeString.toUpperCase()); // ✅ No crash
Open in PlaygroundUnhandled 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.
- ❌ Incorrect
- ✅ 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);
});
Open in Playgrounddeclare 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);
});
Open in PlaygroundIf you only care about promises,
you can use the no-misused-promises rule instead.
Use no-floating-promises
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.
- ❌ Incorrect
- ✅ Correct
declare function takesCallback(cb: () => void): void;
takesCallback(function* () {
console.log('Hello');
yield;
console.log('World');
});
Open in Playgrounddeclare function takesCallback(cb: () => void): void;
takesCallback(() => {
function* gen() {
console.log('Hello');
yield;
console.log('World');
}
for (const _ of gen());
});
Open in PlaygroundAccidental 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.
- ❌ Incorrect
- ✅ Correct
['Kazik', 'Zenek'].forEach(name => `Hello, ${name}!`);
Open in Playground['Kazik', 'Zenek'].forEach(name => console.log(`Hello, ${name}!`));
Open in PlaygroundVoid context from extended classes
This rule enforces class methods which override a void method to also be void.
- ❌ Incorrect
- ✅ Correct
class Foo {
cb() {
console.log('foo');
}
}
class Bar extends Foo {
cb() {
super.cb();
return 'bar';
}
}
Open in Playgroundclass Foo {
cb() {
console.log('foo');
}
}
class Bar extends Foo {
cb() {
super.cb();
console.log('bar');
}
}
Open in PlaygroundVoid context from implemented interfaces
This rule enforces class methods which implement a void method to also be void.
- ❌ Incorrect
- ✅ Correct
interface Foo {
cb(): void;
}
class Bar implements Foo {
cb() {
return 'cb';
}
}
Open in Playgroundinterface Foo {
cb(): void;
}
class Bar implements Foo {
cb() {
console.log('cb');
}
}
Open in PlaygroundOptions
This rule accepts the following options:
type Options = [
{
/** Whether to allow functions returning `any` to be used in place expecting a `void` function. */
allowReturnAny?: boolean;
},
];
const defaultOptions: Options = [{ allowReturnAny: false }];
allowReturnAny
Whether to allow functions returning any to be used in place expecting a void function. Default: false.
Additional incorrect code when the option is disabled:
- ❌ Incorrect
- ✅ Correct
declare function fn(cb: () => void): void;
fn(() => JSON.parse('{}'));
fn(() => {
return someUntypedApi();
});
Open in Playgrounddeclare function fn(cb: () => void): void;
fn(() => void JSON.parse('{}'));
fn(() => {
someUntypedApi();
});
Open in PlaygroundWhen 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 instead.
In browser contexts, 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 cases you can disable this rule.
Similarly, the default server runtime behaviors of crashing the process on unhandled Promise rejections
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- A subset of this rule which only cares about promises.no-floating-promises- Warns about unhandled promises in statement positions.no-confusing-void-expression- Disallows returning void values.
Type checked lint rules are more powerful than traditional lint rules, but also require configuring type checked linting.
See Troubleshooting > Linting with Type Information > Performance if you experience performance degradations after enabling type checked rules.