diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx index 3621829cb977..4ac2c491cde0 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx @@ -37,10 +37,89 @@ If you don't want to check conditionals, you can configure the rule with `"check Doing so prevents the rule from looking at code like `if (somePromise)`. -Examples of code for this rule with `checksConditionals: true`: +### `checksVoidReturn` + +Likewise, if you don't want to check functions that return promises where a void return is +expected, your configuration will look like this: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": false + } + ] +} +``` + +You can disable selective parts of the `checksVoidReturn` option by providing an object that disables specific checks. For example, if you don't mind that passing a `() => Promise` to a `() => void` parameter or JSX attribute can lead to a floating unhandled Promise: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": { + "arguments": false, + "attributes": false + } + } + ] +} +``` + +The following sub-options are supported: + +#### `arguments` + +Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns `void`. + +#### `attributes` + +Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns `void`. + +#### `inheritedMethods` + +Disables checking an asynchronous method in a type that extends or implements another type expecting that method to return `void`. + +:::note +For now, `no-misused-promises` only checks _named_ methods against extended/implemented types: that is, call/construct/index signatures are ignored. Call signatures are not required in TypeScript to be consistent with one another, and construct signatures cannot be `async` in the first place. Index signature checking may be implemented in the future. +::: + +#### `properties` + +Disables checking an asynchronous function passed as an object property expected to be a function that returns `void`. + +#### `returns` + +Disables checking an asynchronous function returned in a function whose return type is a function that returns `void`. + +#### `variables` + +Disables checking an asynchronous function used as a variable whose return type is a function that returns `void`. + +### `checksSpreads` + +If you don't want to check object spreads, you can add this configuration: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksSpreads": false + } + ] +} +``` ## Examples +### `checksConditionals` + +Examples of code for this rule with `checksConditionals: true`: + @@ -81,45 +160,6 @@ while (await promise) { ### `checksVoidReturn` -Likewise, if you don't want to check functions that return promises where a void return is -expected, your configuration will look like this: - -```json -{ - "@typescript-eslint/no-misused-promises": [ - "error", - { - "checksVoidReturn": false - } - ] -} -``` - -You can disable selective parts of the `checksVoidReturn` option by providing an object that disables specific checks. -The following options are supported: - -- `arguments`: Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns `void` -- `attributes`: Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns `void` -- `properties`: Disables checking an asynchronous function passed as an object property expected to be a function that returns `void` -- `returns`: Disables checking an asynchronous function returned in a function whose return type is a function that returns `void` -- `variables`: Disables checking an asynchronous function used as a variable whose return type is a function that returns `void` - -For example, if you don't mind that passing a `() => Promise` to a `() => void` parameter or JSX attribute can lead to a floating unhandled Promise: - -```json -{ - "@typescript-eslint/no-misused-promises": [ - "error", - { - "checksVoidReturn": { - "arguments": false, - "attributes": false - } - } - ] -} -``` - Examples of code for this rule with `checksVoidReturn: true`: @@ -140,6 +180,15 @@ document.addEventListener('click', async () => { await fetch('/'); console.log('synchronous call'); }); + +interface MySyncInterface { + setThing(): void; +} +class MyClass implements MySyncInterface { + async setThing(): Promise { + this.thing = await fetchThing(); + } +} ``` @@ -182,6 +231,15 @@ document.addEventListener('click', () => { handler().catch(handleError); }); + +interface MyAsyncInterface { + setThing(): Promise; +} +class MyClass implements MyAsyncInterface { + async setThing(): Promise { + this.thing = await fetchThing(); + } +} ``` @@ -189,19 +247,6 @@ document.addEventListener('click', () => { ### `checksSpreads` -If you don't want to check object spreads, you can add this configuration: - -```json -{ - "@typescript-eslint/no-misused-promises": [ - "error", - { - "checksSpreads": false - } - ] -} -``` - Examples of code for this rule with `checksSpreads: true`: diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 46d3d9759b1c..a5433edf7d10 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -23,6 +23,7 @@ type Options = [ interface ChecksVoidReturnOptions { arguments?: boolean; attributes?: boolean; + inheritedMethods?: boolean; properties?: boolean; returns?: boolean; variables?: boolean; @@ -33,6 +34,7 @@ type MessageId = | 'spread' | 'voidReturnArgument' | 'voidReturnAttribute' + | 'voidReturnInheritedMethod' | 'voidReturnProperty' | 'voidReturnReturnValue' | 'voidReturnVariable'; @@ -49,6 +51,7 @@ function parseChecksVoidReturn( return { arguments: true, attributes: true, + inheritedMethods: true, properties: true, returns: true, variables: true, @@ -58,6 +61,7 @@ function parseChecksVoidReturn( return { arguments: checksVoidReturn.arguments ?? true, attributes: checksVoidReturn.attributes ?? true, + inheritedMethods: checksVoidReturn.inheritedMethods ?? true, properties: checksVoidReturn.properties ?? true, returns: checksVoidReturn.returns ?? true, variables: checksVoidReturn.variables ?? true, @@ -76,14 +80,16 @@ export default createRule({ messages: { voidReturnArgument: 'Promise returned in function argument where a void return was expected.', - voidReturnVariable: - 'Promise-returning function provided to variable where a void return was expected.', + voidReturnAttribute: + 'Promise-returning function provided to attribute where a void return was expected.', + voidReturnInheritedMethod: + "Promise-returning method provided where a void return was expected by extended/implemented type '{{ heritageTypeName }}'.", voidReturnProperty: 'Promise-returning function provided to property where a void return was expected.', voidReturnReturnValue: 'Promise-returning function provided to return value where a void return was expected.', - voidReturnAttribute: - 'Promise-returning function provided to attribute where a void return was expected.', + voidReturnVariable: + 'Promise-returning function provided to variable where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', spread: 'Expected a non-Promise value to be spreaded in an object.', }, @@ -103,6 +109,7 @@ export default createRule({ properties: { arguments: { type: 'boolean' }, attributes: { type: 'boolean' }, + inheritedMethods: { type: 'boolean' }, properties: { type: 'boolean' }, returns: { type: 'boolean' }, variables: { type: 'boolean' }, @@ -156,6 +163,11 @@ export default createRule({ ...(checksVoidReturn.attributes && { JSXAttribute: checkJSXAttribute, }), + ...(checksVoidReturn.inheritedMethods && { + ClassDeclaration: checkClassLikeOrInterfaceNode, + ClassExpression: checkClassLikeOrInterfaceNode, + TSInterfaceDeclaration: checkClassLikeOrInterfaceNode, + }), ...(checksVoidReturn.properties && { Property: checkProperty, }), @@ -466,6 +478,71 @@ export default createRule({ } } + function checkClassLikeOrInterfaceNode( + node: + | TSESTree.ClassDeclaration + | TSESTree.ClassExpression + | TSESTree.TSInterfaceDeclaration, + ): void { + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + + const heritageTypes = getHeritageTypes(checker, tsNode); + if (!heritageTypes?.length) { + return; + } + + for (const nodeMember of tsNode.members) { + const memberName = nodeMember.name?.getText(); + if (memberName === undefined) { + // Call/construct/index signatures don't have names. TS allows call signatures to mismatch, + // and construct signatures can't be async. + // TODO - Once we're able to use `checker.isTypeAssignableTo` (v8), we can check an index + // signature here against its compatible index signatures in `heritageTypes` + continue; + } + if (!returnsThenable(checker, nodeMember)) { + continue; + } + for (const heritageType of heritageTypes) { + checkHeritageTypeForMemberReturningVoid( + nodeMember, + heritageType, + memberName, + ); + } + } + } + + /** + * Checks `heritageType` for a member named `memberName` that returns void; reports the + * 'voidReturnInheritedMethod' message if found. + * @param nodeMember Node member that returns a Promise + * @param heritageType Heritage type to check against + * @param memberName Name of the member to check for + */ + function checkHeritageTypeForMemberReturningVoid( + nodeMember: ts.Node, + heritageType: ts.Type, + memberName: string, + ): void { + const heritageMember = getMemberIfExists(heritageType, memberName); + if (heritageMember === undefined) { + return; + } + const memberType = checker.getTypeOfSymbolAtLocation( + heritageMember, + nodeMember, + ); + if (!isVoidReturningFunctionType(checker, nodeMember, memberType)) { + return; + } + context.report({ + node: services.tsNodeToESTreeNodeMap.get(nodeMember), + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: checker.typeToString(heritageType) }, + }); + } + function checkJSXAttribute(node: TSESTree.JSXAttribute): void { if ( node.value == null || @@ -777,3 +854,26 @@ function returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean { .unionTypeParts(type) .some(t => anySignatureIsThenableType(checker, node, t)); } + +function getHeritageTypes( + checker: ts.TypeChecker, + tsNode: ts.ClassDeclaration | ts.ClassExpression | ts.InterfaceDeclaration, +): ts.Type[] | undefined { + return tsNode.heritageClauses + ?.flatMap(clause => clause.types) + .map(typeExpression => checker.getTypeAtLocation(typeExpression)); +} + +/** + * @returns The member with the given name in `type`, if it exists. + */ +function getMemberIfExists( + type: ts.Type, + memberName: string, +): ts.Symbol | undefined { + const escapedMemberName = ts.escapeLeadingUnderscores(memberName); + const symbolMemberMatch = type.getSymbol()?.members?.get(escapedMemberName); + return ( + symbolMemberMatch ?? tsutils.getPropertyOfType(type, escapedMemberName) + ); +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot index e42445e017bb..17d39d27c875 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot @@ -65,6 +65,18 @@ document.addEventListener('click', async () => { await fetch('/'); console.log('synchronous call'); }); + +interface MySyncInterface { + setThing(): void; +} +class MyClass implements MySyncInterface { + async setThing(): Promise { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Promise-returning method provided where a void return was expected by extended/implemented type 'MySyncInterface'. + this.thing = await fetchThing(); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + } +~~~ +} " `; @@ -108,6 +120,15 @@ document.addEventListener('click', () => { handler().catch(handleError); }); + +interface MyAsyncInterface { + setThing(): Promise; +} +class MyClass implements MyAsyncInterface { + async setThing(): Promise { + this.thing = await fetchThing(); + } +} " `; diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index ce7f5c0eef90..fc2229b31cf4 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -508,794 +508,1739 @@ foo(bar); }, options: [{ checksVoidReturn: { attributes: true } }], }, - ` - const notAFn1: string = ''; - const notAFn2: number = 1; - const notAFn3: boolean = true; - const notAFn4: { prop: 1 } = { prop: 1 }; - const notAFn5: {} = {}; - const notAFn5: {} = {}; - `, - ], - - invalid: [ { code: ` -if (Promise.resolve()) { +class MyClass { + setThing(): void { + return; + } +} + +class MySubclassExtendsMyClass extends MyClass { + setThing(): void { + return; + } } `, - errors: [ - { - line: 2, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -if (Promise.resolve()) { -} else if (Promise.resolve()) { -} else { +class MyClass { + async setThing(): Promise { + await Promise.resolve(); + } +} + +class MySubclassExtendsMyClass extends MyClass { + async setThing(): Promise { + await Promise.resolve(); + } } `, - errors: [ - { - line: 2, - messageId: 'conditional', - }, - { - line: 3, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { - code: 'for (let i; Promise.resolve(); i++) {}', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], + code: ` +class MyClass { + setThing(): void { + return; + } +} + +class MySubclassExtendsMyClass extends MyClass { + async setThing(): Promise { + await Promise.resolve(); + } +} + `, + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { - code: 'do {} while (Promise.resolve());', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], + code: ` +class MyClass { + setThing(): void { + return; + } +} + +abstract class MyAbstractClassExtendsMyClass extends MyClass { + abstract setThing(): void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { - code: 'while (Promise.resolve()) {}', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], + code: ` +class MyClass { + setThing(): void { + return; + } +} + +abstract class MyAbstractClassExtendsMyClass extends MyClass { + abstract setThing(): Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { - code: 'Promise.resolve() ? 123 : 456;', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], + code: ` +class MyClass { + setThing(): void { + return; + } +} + +interface MyInterfaceExtendsMyClass extends MyClass { + setThing(): void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -if (!Promise.resolve()) { +class MyClass { + setThing(): void { + return; + } +} + +interface MyInterfaceExtendsMyClass extends MyClass { + setThing(): Promise; } `, - errors: [ - { - line: 2, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { - code: 'Promise.resolve() || false;', - errors: [ - { - line: 1, - messageId: 'conditional', - }, - ], + code: ` +abstract class MyAbstractClass { + abstract setThing(): void; +} + +class MySubclassExtendsMyAbstractClass extends MyAbstractClass { + setThing(): void { + return; + } +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -[Promise.resolve(), Promise.reject()].forEach(async val => { - await val; -}); +abstract class MyAbstractClass { + abstract setThing(): void; +} + +class MySubclassExtendsMyAbstractClass extends MyAbstractClass { + async setThing(): Promise { + await Promise.resolve(); + } +} `, - errors: [ - { - line: 2, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -new Promise(async (resolve, reject) => { - await Promise.resolve(); - resolve(); -}); +abstract class MyAbstractClass { + abstract setThing(): void; +} + +abstract class MyAbstractSubclassExtendsMyAbstractClass extends MyAbstractClass { + abstract setThing(): void; +} `, - errors: [ - { - line: 2, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { - cb(null, arg); -}; +abstract class MyAbstractClass { + abstract setThing(): void; +} -fnWithCallback('val', async (err, res) => { - await res; -}); +abstract class MyAbstractSubclassExtendsMyAbstractClass extends MyAbstractClass { + abstract setThing(): Promise; +} `, - errors: [ - { - line: 6, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { - cb(null, arg); -}; +abstract class MyAbstractClass { + abstract setThing(): void; +} -fnWithCallback('val', (err, res) => Promise.resolve(res)); +interface MyInterfaceExtendsMyAbstractClass extends MyAbstractClass { + setThing(): void; +} `, - errors: [ - { - line: 6, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { - cb(null, arg); -}; +abstract class MyAbstractClass { + abstract setThing(): void; +} -fnWithCallback('val', (err, res) => { - if (err) { - return 'abc'; - } else { - return Promise.resolve(res); - } -}); +interface MyInterfaceExtendsMyAbstractClass extends MyAbstractClass { + setThing(): Promise; +} `, - errors: [ - { - line: 6, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -const fnWithCallback: - | ((arg: string, cb: (err: any, res: string) => void) => void) - | null = (arg, cb) => { - cb(null, arg); -}; +interface MyInterface { + setThing(): void; +} -fnWithCallback?.('val', (err, res) => Promise.resolve(res)); +interface MySubInterfaceExtendsMyInterface extends MyInterface { + setThing(): void; +} `, - errors: [ - { - line: 8, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -const fnWithCallback: - | ((arg: string, cb: (err: any, res: string) => void) => void) - | null = (arg, cb) => { - cb(null, arg); -}; +interface MyInterface { + setThing(): void; +} -fnWithCallback('val', (err, res) => { - if (err) { - return 'abc'; - } else { - return Promise.resolve(res); - } -}); +interface MySubInterfaceExtendsMyInterface extends MyInterface { + setThing(): Promise; +} `, - errors: [ - { - line: 8, - messageId: 'voidReturnArgument', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -function test(bool: boolean, p: Promise) { - if (bool || p) { +interface MyInterface { + setThing(): void; +} + +class MyClassImplementsMyInterface implements MyInterface { + setThing(): void { + return; } } `, - errors: [ - { - line: 3, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -function test(bool: boolean, p: Promise) { - if (bool && p) { +interface MyInterface { + setThing(): void; +} + +class MyClassImplementsMyInterface implements MyInterface { + async setThing(): Promise { + await Promise.resolve(); } } `, - errors: [ - { - line: 3, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -function test(a: any, p: Promise) { - if (a ?? p) { - } +interface MyInterface { + setThing(): void; +} + +abstract class MyAbstractClassImplementsMyInterface implements MyInterface { + abstract setThing(): void; } `, - errors: [ - { - line: 3, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -function test(p: Promise | undefined) { - if (p ?? Promise.reject()) { - } +interface MyInterface { + setThing(): void; +} + +abstract class MyAbstractClassImplementsMyInterface implements MyInterface { + abstract setThing(): Promise; } `, - errors: [ - { - line: 3, - messageId: 'conditional', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: false } }], }, { code: ` -let f: () => void; -f = async () => { - return 3; +type MyTypeLiteralsIntersection = { setThing(): void } & { thing: number }; + +class MyClass implements MyTypeLiteralsIntersection { + thing = 1; + setThing(): void { + return; + } +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +type MyTypeLiteralsIntersection = { setThing(): void } & { thing: number }; + +class MyClass implements MyTypeLiteralsIntersection { + thing = 1; + async setThing(): Promise { + await Promise.resolve(); + } +} + `, + options: [{ checksVoidReturn: { inheritedMethods: false } }], + }, + { + code: ` +type MyGenericType = IsAsync extends true + ? { setThing(): Promise } + : { setThing(): void }; + +interface MyAsyncInterface extends MyGenericType { + setThing(): Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +type MyGenericType = IsAsync extends true + ? { setThing(): Promise } + : { setThing(): void }; + +interface MyAsyncInterface extends MyGenericType { + setThing(): Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: false } }], + }, + { + code: ` +interface MyInterface { + setThing(): void; +} + +interface MyOtherInterface { + setThing(): void; +} + +interface MyThirdInterface extends MyInterface, MyOtherInterface { + setThing(): void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +class MyClass { + setThing(): void { + return; + } +} + +class MyOtherClass { + setThing(): void { + return; + } +} + +interface MyInterface extends MyClass, MyOtherClass { + setThing(): void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MyInterface { + setThing(): void; +} + +interface MyOtherInterface { + setThing(): void; +} + +class MyClass { + setThing(): void { + return; + } +} + +class MySubclass extends MyClass implements MyInterface, MyOtherInterface { + setThing(): void { + return; + } +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +class MyClass { + setThing(): void { + return; + } +} + +const MyClassExpressionExtendsMyClass = class extends MyClass { + setThing(): void { + return; + } }; `, - errors: [ - { - line: 3, - messageId: 'voidReturnVariable', - }, - ], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -let f: () => void; -f = async () => { - return 3; +const MyClassExpression = class { + setThing(): void { + return; + } }; + +class MyClassExtendsMyClassExpression extends MyClassExpression { + setThing(): void { + return; + } +} `, - errors: [ - { - line: 3, - messageId: 'voidReturnVariable', - }, - ], - options: [{ checksVoidReturn: { variables: true } }], + options: [{ checksVoidReturn: { inheritedMethods: true } }], }, { code: ` -const f: () => void = async () => { - return 0; +const MyClassExpression = class { + setThing(): void { + return; + } }; -const g = async () => 1, - h: () => void = async () => {}; +type MyClassExpressionType = typeof MyClassExpression; + +interface MyInterfaceExtendsMyClassExpression extends MyClassExpressionType { + setThing(): void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MySyncCallSignatures { + (): void; + (arg: string): void; +} +interface MyAsyncInterface extends MySyncCallSignatures { + (): Promise; + (arg: string): Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MySyncConstructSignatures { + new (): void; + new (arg: string): void; +} +interface ThisIsADifferentIssue extends MySyncConstructSignatures { + new (): Promise; + new (arg: string): Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MySyncIndexSignatures { + [key: string]: void; + [key: number]: void; +} +interface ThisIsADifferentIssue extends MySyncIndexSignatures { + [key: string]: Promise; + [key: number]: Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MySyncInterfaceSignatures { + (): void; + (arg: string): void; + new (): void; + [key: string]: () => void; + [key: number]: () => void; +} +interface MyAsyncInterface extends MySyncInterfaceSignatures { + (): Promise; + (arg: string): Promise; + new (): Promise; + [key: string]: () => Promise; + [key: number]: () => Promise; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + { + code: ` +interface MyCall { + (): void; + (arg: string): void; +} + +interface MyIndex { + [key: string]: () => void; + [key: number]: () => void; +} + +interface MyConstruct { + new (): void; + new (arg: string): void; +} + +interface MyMethods { + doSyncThing(): void; + doOtherSyncThing(): void; + syncMethodProperty: () => void; +} +interface MyInterface extends MyCall, MyIndex, MyConstruct, MyMethods { + (): void; + (arg: string): void; + new (): void; + new (arg: string): void; + [key: string]: () => void; + [key: number]: () => void; + doSyncThing(): void; + doAsyncThing(): Promise; + syncMethodProperty: () => void; +} + `, + options: [{ checksVoidReturn: { inheritedMethods: true } }], + }, + "const notAFn1: string = '';", + 'const notAFn2: number = 1;', + 'const notAFn3: boolean = true;', + 'const notAFn4: { prop: 1 } = { prop: 1 };', + 'const notAFn5: {} = {};', + ], + + invalid: [ + { + code: ` +if (Promise.resolve()) { +} `, errors: [ { line: 2, - messageId: 'voidReturnVariable', - }, - { - line: 6, - messageId: 'voidReturnVariable', + messageId: 'conditional', }, ], }, { code: ` -const obj: { - f?: () => void; -} = {}; -obj.f = async () => { - return 0; -}; +if (Promise.resolve()) { +} else if (Promise.resolve()) { +} else { +} `, errors: [ { - line: 5, - messageId: 'voidReturnVariable', + line: 2, + messageId: 'conditional', + }, + { + line: 3, + messageId: 'conditional', }, ], }, { - code: ` -type O = { f: () => void }; -const obj: O = { - f: async () => 'foo', -}; - `, + code: 'for (let i; Promise.resolve(); i++) {}', errors: [ { - line: 4, - messageId: 'voidReturnProperty', + line: 1, + messageId: 'conditional', }, ], }, { - code: ` -type O = { f: () => void }; -const obj: O = { - f: async () => 'foo', -}; - `, + code: 'do {} while (Promise.resolve());', errors: [ { - line: 4, - messageId: 'voidReturnProperty', + line: 1, + messageId: 'conditional', }, ], - options: [{ checksVoidReturn: { properties: true } }], }, { - code: ` -type O = { f: () => void }; -const f = async () => 0; -const obj: O = { - f, -}; - `, + code: 'while (Promise.resolve()) {}', errors: [ { - line: 5, - messageId: 'voidReturnProperty', + line: 1, + messageId: 'conditional', }, ], }, { - code: ` -type O = { f: () => void }; -const obj: O = { - async f() { - return 0; - }, -}; - `, + code: 'Promise.resolve() ? 123 : 456;', errors: [ { - line: 4, - messageId: 'voidReturnProperty', + line: 1, + messageId: 'conditional', }, ], }, { code: ` -type O = { f: () => void; g: () => void; h: () => void }; -function f(): O { - const h = async () => 0; - return { - async f() { - return 123; - }, - g: async () => 0, - h, - }; +if (!Promise.resolve()) { } `, errors: [ { - line: 6, - messageId: 'voidReturnProperty', - }, - { - line: 9, - messageId: 'voidReturnProperty', - }, - { - line: 10, - messageId: 'voidReturnProperty', + line: 2, + messageId: 'conditional', }, ], }, { - code: ` -function f(): () => void { - return async () => 0; -} - `, + code: 'Promise.resolve() || false;', errors: [ { - line: 3, - messageId: 'voidReturnReturnValue', + line: 1, + messageId: 'conditional', }, ], }, { code: ` -function f(): () => void { - return async () => 0; -} +[Promise.resolve(), Promise.reject()].forEach(async val => { + await val; +}); `, errors: [ { - line: 3, - messageId: 'voidReturnReturnValue', + line: 2, + messageId: 'voidReturnArgument', }, ], - options: [{ checksVoidReturn: { returns: true } }], }, { code: ` -type O = { - func: () => void; -}; -const Component = (obj: O) => null; - 0} />; +new Promise(async (resolve, reject) => { + await Promise.resolve(); + resolve(); +}); `, - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, + errors: [ + { + line: 2, + messageId: 'voidReturnArgument', }, - }, + ], + }, + { + code: ` +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); +}; + +fnWithCallback('val', async (err, res) => { + await res; +}); + `, errors: [ { line: 6, - messageId: 'voidReturnAttribute', + messageId: 'voidReturnArgument', }, ], }, { code: ` -type O = { - func: () => void; +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); }; -const Component = (obj: O) => null; - 0} />; + +fnWithCallback('val', (err, res) => Promise.resolve(res)); `, - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, errors: [ { line: 6, - messageId: 'voidReturnAttribute', + messageId: 'voidReturnArgument', }, ], - options: [{ checksVoidReturn: { attributes: true } }], }, { code: ` -type O = { - func: () => void; +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); }; -const g = async () => 'foo'; -const Component = (obj: O) => null; -; + +fnWithCallback('val', (err, res) => { + if (err) { + return 'abc'; + } else { + return Promise.resolve(res); + } +}); `, - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, errors: [ { - line: 7, - messageId: 'voidReturnAttribute', + line: 6, + messageId: 'voidReturnArgument', }, ], }, { code: ` -interface ItLike { - (name: string, callback: () => number): void; - (name: string, callback: () => void): void; -} +const fnWithCallback: + | ((arg: string, cb: (err: any, res: string) => void) => void) + | null = (arg, cb) => { + cb(null, arg); +}; -declare const it: ItLike; +fnWithCallback?.('val', (err, res) => Promise.resolve(res)); + `, + errors: [ + { + line: 8, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +const fnWithCallback: + | ((arg: string, cb: (err: any, res: string) => void) => void) + | null = (arg, cb) => { + cb(null, arg); +}; -it('', async () => {}); +fnWithCallback('val', (err, res) => { + if (err) { + return 'abc'; + } else { + return Promise.resolve(res); + } +}); `, errors: [ { - line: 9, + line: 8, messageId: 'voidReturnArgument', }, ], }, { code: ` -interface ItLike { - (name: string, callback: () => number): void; +function test(bool: boolean, p: Promise) { + if (bool || p) { + } } -interface ItLike { - (name: string, callback: () => void): void; + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(bool: boolean, p: Promise) { + if (bool && p) { + } } - -declare const it: ItLike; - -it('', async () => {}); `, errors: [ { - line: 11, - messageId: 'voidReturnArgument', + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(a: any, p: Promise) { + if (a ?? p) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +function test(p: Promise | undefined) { + if (p ?? Promise.reject()) { + } +} + `, + errors: [ + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: ` +let f: () => void; +f = async () => { + return 3; +}; + `, + errors: [ + { + line: 3, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +let f: () => void; +f = async () => { + return 3; +}; + `, + errors: [ + { + line: 3, + messageId: 'voidReturnVariable', + }, + ], + options: [{ checksVoidReturn: { variables: true } }], + }, + { + code: ` +const f: () => void = async () => { + return 0; +}; +const g = async () => 1, + h: () => void = async () => {}; + `, + errors: [ + { + line: 2, + messageId: 'voidReturnVariable', + }, + { + line: 6, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +const obj: { + f?: () => void; +} = {}; +obj.f = async () => { + return 0; +}; + `, + errors: [ + { + line: 5, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + f: async () => 'foo', +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + f: async () => 'foo', +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + options: [{ checksVoidReturn: { properties: true } }], + }, + { + code: ` +type O = { f: () => void }; +const f = async () => 0; +const obj: O = { + f, +}; + `, + errors: [ + { + line: 5, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + async f() { + return 0; + }, +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void; g: () => void; h: () => void }; +function f(): O { + const h = async () => 0; + return { + async f() { + return 123; + }, + g: async () => 0, + h, + }; +} + `, + errors: [ + { + line: 6, + messageId: 'voidReturnProperty', + }, + { + line: 9, + messageId: 'voidReturnProperty', + }, + { + line: 10, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +function f(): () => void { + return async () => 0; +} + `, + errors: [ + { + line: 3, + messageId: 'voidReturnReturnValue', + }, + ], + }, + { + code: ` +function f(): () => void { + return async () => 0; +} + `, + errors: [ + { + line: 3, + messageId: 'voidReturnReturnValue', + }, + ], + options: [{ checksVoidReturn: { returns: true } }], + }, + { + code: ` +type O = { + func: () => void; +}; +const Component = (obj: O) => null; + 0} />; + `, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + errors: [ + { + line: 6, + messageId: 'voidReturnAttribute', + }, + ], + }, + { + code: ` +type O = { + func: () => void; +}; +const Component = (obj: O) => null; + 0} />; + `, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + errors: [ + { + line: 6, + messageId: 'voidReturnAttribute', + }, + ], + options: [{ checksVoidReturn: { attributes: true } }], + }, + { + code: ` +type O = { + func: () => void; +}; +const g = async () => 'foo'; +const Component = (obj: O) => null; +; + `, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + errors: [ + { + line: 7, + messageId: 'voidReturnAttribute', + }, + ], + }, + { + code: ` +interface ItLike { + (name: string, callback: () => number): void; + (name: string, callback: () => void): void; +} + +declare const it: ItLike; + +it('', async () => {}); + `, + errors: [ + { + line: 9, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +interface ItLike { + (name: string, callback: () => number): void; +} +interface ItLike { + (name: string, callback: () => void): void; +} + +declare const it: ItLike; + +it('', async () => {}); + `, + errors: [ + { + line: 11, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +interface ItLike { + (name: string, callback: () => void): void; +} +interface ItLike { + (name: string, callback: () => number): void; +} + +declare const it: ItLike; + +it('', async () => {}); + `, + errors: [ + { + line: 11, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +console.log({ ...Promise.resolve({ key: 42 }) }); + `, + errors: [ + { + line: 2, + messageId: 'spread', + }, + ], + }, + { + code: ` +const getData = () => Promise.resolve({ key: 42 }); + +console.log({ + someData: 42, + ...getData(), +}); + `, + errors: [ + { + line: 6, + messageId: 'spread', + }, + ], + }, + { + code: ` +declare const condition: boolean; + +console.log({ ...(condition && Promise.resolve({ key: 42 })) }); +console.log({ ...(condition || Promise.resolve({ key: 42 })) }); +console.log({ ...(condition ? {} : Promise.resolve({ key: 42 })) }); +console.log({ ...(condition ? Promise.resolve({ key: 42 }) : {}) }); + `, + errors: [ + { line: 4, messageId: 'spread' }, + { line: 5, messageId: 'spread' }, + { line: 6, messageId: 'spread' }, + { line: 7, messageId: 'spread' }, + ], + }, + { + code: ` +function restPromises(first: Boolean, ...callbacks: Array<() => void>): void {} + +restPromises( + true, + () => Promise.resolve(true), + () => Promise.resolve(null), + () => true, + () => Promise.resolve('Hello'), +); + `, + errors: [ + { line: 6, messageId: 'voidReturnArgument' }, + { line: 7, messageId: 'voidReturnArgument' }, + { line: 9, messageId: 'voidReturnArgument' }, + ], + }, + { + code: ` +type MyUnion = (() => void) | boolean; + +function restUnion(first: string, ...callbacks: Array): void {} +restUnion('Testing', false, () => Promise.resolve(true)); + `, + errors: [{ line: 5, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function restTupleOne(first: string, ...callbacks: [() => void]): void {} +restTupleOne('My string', () => Promise.resolve(1)); + `, + errors: [{ line: 3, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function restTupleTwo( + first: boolean, + ...callbacks: [undefined, () => void, undefined] +): void {} + +restTupleTwo(true, undefined, () => Promise.resolve(true), undefined); + `, + errors: [{ line: 7, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function restTupleFour( + first: number, + ...callbacks: [() => void, boolean, () => void, () => void] +): void; + +restTupleFour( + 1, + () => Promise.resolve(true), + false, + () => {}, + () => Promise.resolve(1), +); + `, + errors: [ + { line: 9, messageId: 'voidReturnArgument' }, + { line: 12, messageId: 'voidReturnArgument' }, + ], + }, + { + // Prettier adds a () but this tests arguments being undefined, not [] + // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting + code: ` +class TakesVoidCb { + constructor(first: string, ...args: Array<() => void>); +} + +new TakesVoidCb; +new TakesVoidCb(); +new TakesVoidCb( + 'Testing', + () => {}, + () => Promise.resolve(true), +); + `, + errors: [{ line: 11, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function restTuple(...args: []): void; +function restTuple(...args: [boolean, () => void]): void; +function restTuple(..._args: any[]): void {} + +restTuple(); +restTuple(true, () => Promise.resolve(1)); + `, + errors: [{ line: 7, messageId: 'voidReturnArgument' }], + }, + { + code: ` +type ReturnsRecord = () => Record void>; + +const test: ReturnsRecord = () => { + return { asynchronous: async () => {} }; +}; + `, + errors: [{ line: 5, messageId: 'voidReturnProperty' }], + }, + { + code: ` +let value: Record void>; +value.asynchronous = async () => {}; + `, + errors: [{ line: 3, messageId: 'voidReturnVariable' }], + }, + { + code: ` +type ReturnsRecord = () => Record void>; + +async function asynchronous() {} + +const test: ReturnsRecord = () => { + return { asynchronous }; +}; + `, + errors: [{ line: 7, messageId: 'voidReturnProperty' }], + }, + { + code: ` +declare function foo(cb: undefined | (() => void)); +declare const bar: undefined | (() => Promise); +foo(bar); + `, + errors: [{ line: 4, messageId: 'voidReturnArgument' }], + }, + { + code: ` +declare function foo(cb: string & (() => void)); +declare const bar: string & (() => Promise); +foo(bar); + `, + errors: [{ line: 4, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function consume(..._callbacks: Array<() => void>): void {} +let cbs: Array<() => Promise> = [ + () => Promise.resolve(true), + () => Promise.resolve(true), +]; +consume(...cbs); + `, + errors: [{ line: 7, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function consume(..._callbacks: Array<() => void>): void {} +let cbs = [() => Promise.resolve(true), () => Promise.resolve(true)] as const; +consume(...cbs); + `, + errors: [{ line: 4, messageId: 'voidReturnArgument' }], + }, + { + code: ` +function consume(..._callbacks: Array<() => void>): void {} +let cbs = [() => Promise.resolve(true), () => Promise.resolve(true)]; +consume(...cbs); + `, + errors: [{ line: 4, messageId: 'voidReturnArgument' }], + }, + { + code: ` +class MyClass { + setThing(): void { + return; + } +} + +class MySubclassExtendsMyClass extends MyClass { + async setThing(): Promise { + await Promise.resolve(); + } +} + `, + errors: [ + { + line: 9, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClass' }, + }, + ], + }, + { + code: ` +class MyClass { + setThing(): void { + return; + } +} + +abstract class MyAbstractClassExtendsMyClass extends MyClass { + abstract setThing(): Promise; +} + `, + errors: [ + { + line: 9, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClass' }, + }, + ], + }, + { + code: ` +class MyClass { + setThing(): void { + return; + } +} + +interface MyInterfaceExtendsMyClass extends MyClass { + setThing(): Promise; +} + `, + errors: [ + { + line: 9, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClass' }, }, ], }, { code: ` -interface ItLike { - (name: string, callback: () => void): void; -} -interface ItLike { - (name: string, callback: () => number): void; +abstract class MyAbstractClass { + abstract setThing(): void; } -declare const it: ItLike; - -it('', async () => {}); +class MySubclassExtendsMyAbstractClass extends MyAbstractClass { + async setThing(): Promise { + await Promise.resolve(); + } +} `, errors: [ { - line: 11, - messageId: 'voidReturnArgument', + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyAbstractClass' }, }, ], }, { code: ` -console.log({ ...Promise.resolve({ key: 42 }) }); +abstract class MyAbstractClass { + abstract setThing(): void; +} + +abstract class MyAbstractSubclassExtendsMyAbstractClass extends MyAbstractClass { + abstract setThing(): Promise; +} `, errors: [ { - line: 2, - messageId: 'spread', + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyAbstractClass' }, }, ], }, { code: ` -const getData = () => Promise.resolve({ key: 42 }); +abstract class MyAbstractClass { + abstract setThing(): void; +} -console.log({ - someData: 42, - ...getData(), -}); +interface MyInterfaceExtendsMyAbstractClass extends MyAbstractClass { + setThing(): Promise; +} `, errors: [ { - line: 6, - messageId: 'spread', + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyAbstractClass' }, }, ], }, { code: ` -declare const condition: boolean; +interface MyInterface { + setThing(): void; +} -console.log({ ...(condition && Promise.resolve({ key: 42 })) }); -console.log({ ...(condition || Promise.resolve({ key: 42 })) }); -console.log({ ...(condition ? {} : Promise.resolve({ key: 42 })) }); -console.log({ ...(condition ? Promise.resolve({ key: 42 }) : {}) }); +class MyInterfaceSubclass implements MyInterface { + async setThing(): Promise { + await Promise.resolve(); + } +} `, errors: [ - { line: 4, messageId: 'spread' }, - { line: 5, messageId: 'spread' }, - { line: 6, messageId: 'spread' }, - { line: 7, messageId: 'spread' }, + { + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyInterface' }, + }, ], }, { code: ` -function restPromises(first: Boolean, ...callbacks: Array<() => void>): void {} +interface MyInterface { + setThing(): void; +} -restPromises( - true, - () => Promise.resolve(true), - () => Promise.resolve(null), - () => true, - () => Promise.resolve('Hello'), -); +abstract class MyAbstractClassImplementsMyInterface implements MyInterface { + abstract setThing(): Promise; +} `, errors: [ - { line: 6, messageId: 'voidReturnArgument' }, - { line: 7, messageId: 'voidReturnArgument' }, - { line: 9, messageId: 'voidReturnArgument' }, + { + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyInterface' }, + }, ], }, { code: ` -type MyUnion = (() => void) | boolean; +interface MyInterface { + setThing(): void; +} -function restUnion(first: string, ...callbacks: Array): void {} -restUnion('Testing', false, () => Promise.resolve(true)); - `, - errors: [{ line: 5, messageId: 'voidReturnArgument' }], - }, - { - code: ` -function restTupleOne(first: string, ...callbacks: [() => void]): void {} -restTupleOne('My string', () => Promise.resolve(1)); +interface MySubInterface extends MyInterface { + setThing(): Promise; +} `, - errors: [{ line: 3, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyInterface' }, + }, + ], }, { code: ` -function restTupleTwo( - first: boolean, - ...callbacks: [undefined, () => void, undefined] -): void {} +type MyTypeIntersection = { setThing(): void } & { thing: number }; -restTupleTwo(true, undefined, () => Promise.resolve(true), undefined); +class MyClassImplementsMyTypeIntersection implements MyTypeIntersection { + thing = 1; + async setThing(): Promise { + await Promise.resolve(); + } +} `, - errors: [{ line: 7, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 6, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyTypeIntersection' }, + }, + ], }, { code: ` -function restTupleFour( - first: number, - ...callbacks: [() => void, boolean, () => void, () => void] -): void; +type MyGenericType = IsAsync extends true + ? { setThing(): Promise } + : { setThing(): void }; -restTupleFour( - 1, - () => Promise.resolve(true), - false, - () => {}, - () => Promise.resolve(1), -); +interface MyAsyncInterface extends MyGenericType { + setThing(): Promise; +} `, errors: [ - { line: 9, messageId: 'voidReturnArgument' }, - { line: 12, messageId: 'voidReturnArgument' }, + { + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: '{ setThing(): void; }' }, + }, ], }, { - // Prettier adds a () but this tests arguments being undefined, not [] - // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting code: ` -class TakesVoidCb { - constructor(first: string, ...args: Array<() => void>); +interface MyInterface { + setThing(): void; } -new TakesVoidCb; -new TakesVoidCb(); -new TakesVoidCb( - 'Testing', - () => {}, - () => Promise.resolve(true), -); - `, - errors: [{ line: 11, messageId: 'voidReturnArgument' }], - }, - { - code: ` -function restTuple(...args: []): void; -function restTuple(...args: [boolean, () => void]): void; -function restTuple(..._args: any[]): void {} +interface MyOtherInterface { + setThing(): void; +} -restTuple(); -restTuple(true, () => Promise.resolve(1)); +interface MyThirdInterface extends MyInterface, MyOtherInterface { + setThing(): Promise; +} `, - errors: [{ line: 7, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 11, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyInterface' }, + }, + { + line: 11, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyOtherInterface' }, + }, + ], }, { code: ` -type ReturnsRecord = () => Record void>; +class MyClass { + setThing(): void { + return; + } +} -const test: ReturnsRecord = () => { - return { asynchronous: async () => {} }; -}; - `, - errors: [{ line: 5, messageId: 'voidReturnProperty' }], - }, - { - code: ` -let value: Record void>; -value.asynchronous = async () => {}; +class MyOtherClass { + setThing(): void { + return; + } +} + +interface MyInterface extends MyClass, MyOtherClass { + setThing(): Promise; +} `, - errors: [{ line: 3, messageId: 'voidReturnVariable' }], + errors: [ + { + line: 15, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClass' }, + }, + { + line: 15, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyOtherClass' }, + }, + ], }, { code: ` -type ReturnsRecord = () => Record void>; +interface MyAsyncInterface { + setThing(): Promise; +} -async function asynchronous() {} +interface MySyncInterface { + setThing(): void; +} -const test: ReturnsRecord = () => { - return { asynchronous }; -}; +class MyClass { + setThing(): void { + return; + } +} + +class MySubclass extends MyClass implements MyAsyncInterface, MySyncInterface { + async setThing(): Promise { + await Promise.resolve(); + } +} `, - errors: [{ line: 7, messageId: 'voidReturnProperty' }], + errors: [ + { + line: 17, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClass' }, + }, + { + line: 17, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MySyncInterface' }, + }, + ], }, { code: ` -declare function foo(cb: undefined | (() => void)); -declare const bar: undefined | (() => Promise); -foo(bar); +interface MyInterface { + setThing(): void; +} + +const MyClassExpressionExtendsMyClass = class implements MyInterface { + setThing(): Promise { + await Promise.resolve(); + } +}; `, - errors: [{ line: 4, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyInterface' }, + }, + ], }, { code: ` -declare function foo(cb: string & (() => void)); -declare const bar: string & (() => Promise); -foo(bar); +const MyClassExpression = class { + setThing(): void { + return; + } +}; + +class MyClassExtendsMyClassExpression extends MyClassExpression { + async setThing(): Promise { + await Promise.resolve(); + } +} `, - errors: [{ line: 4, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 9, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyClassExpression' }, + }, + ], }, { code: ` -function consume(..._callbacks: Array<() => void>): void {} -let cbs: Array<() => Promise> = [ - () => Promise.resolve(true), - () => Promise.resolve(true), -]; -consume(...cbs); +const MyClassExpression = class { + setThing(): void { + return; + } +}; +type MyClassExpressionType = typeof MyClassExpression; + +interface MyInterfaceExtendsMyClassExpression extends MyClassExpressionType { + setThing(): Promise; +} `, - errors: [{ line: 7, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 10, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'typeof MyClassExpression' }, + }, + ], }, { code: ` -function consume(..._callbacks: Array<() => void>): void {} -let cbs = [() => Promise.resolve(true), () => Promise.resolve(true)] as const; -consume(...cbs); +interface MySyncInterface { + (): void; + (arg: string): void; + new (): void; + [key: string]: () => void; + [key: number]: () => void; + myMethod(): void; +} +interface MyAsyncInterface extends MySyncInterface { + (): Promise; + (arg: string): Promise; + new (): Promise; + [key: string]: () => Promise; + [key: number]: () => Promise; + myMethod(): Promise; +} `, - errors: [{ line: 4, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 16, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MySyncInterface' }, + }, + ], }, { code: ` -function consume(..._callbacks: Array<() => void>): void {} -let cbs = [() => Promise.resolve(true), () => Promise.resolve(true)]; -consume(...cbs); +interface MyCall { + (): void; + (arg: string): void; +} + +interface MyIndex { + [key: string]: () => void; + [key: number]: () => void; +} + +interface MyConstruct { + new (): void; + new (arg: string): void; +} + +interface MyMethods { + doSyncThing(): void; + doOtherSyncThing(): void; + syncMethodProperty: () => void; +} +interface MyInterface extends MyCall, MyIndex, MyConstruct, MyMethods { + (): void; + (arg: string): Promise; + new (): void; + new (arg: string): void; + [key: string]: () => Promise; + [key: number]: () => void; + doSyncThing(): Promise; + doAsyncThing(): Promise; + syncMethodProperty: () => Promise; +} `, - errors: [{ line: 4, messageId: 'voidReturnArgument' }], + errors: [ + { + line: 29, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyMethods' }, + }, + { + line: 31, + messageId: 'voidReturnInheritedMethod', + data: { heritageTypeName: 'MyMethods' }, + }, + ], }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-misused-promises.shot b/packages/eslint-plugin/tests/schema-snapshots/no-misused-promises.shot index 2ba6191f4645..4cd7d094b174 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-misused-promises.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-misused-promises.shot @@ -28,6 +28,9 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "attributes": { "type": "boolean" }, + "inheritedMethods": { + "type": "boolean" + }, "properties": { "type": "boolean" }, @@ -58,6 +61,7 @@ type Options = [ | { arguments?: boolean; attributes?: boolean; + inheritedMethods?: boolean; properties?: boolean; returns?: boolean; variables?: boolean; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy