diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index d806f4c59bfa..d48bc0d3d428 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -4,10 +4,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; +import type { NodeWithKey } from '../util'; + import { createRule, getFunctionHeadLoc, getParserServices, + getStaticMemberAccessValue, isArrayMethodCallWithPredicate, isFunction, isRestParameterDeclaration, @@ -470,9 +473,6 @@ export default createRule({ }); } } else if (ts.isMethodDeclaration(tsNode)) { - if (ts.isComputedPropertyName(tsNode.name)) { - return; - } const obj = tsNode.parent; // Below condition isn't satisfied unless something goes wrong, @@ -492,9 +492,14 @@ export default createRule({ if (objType == null) { return; } - const propertySymbol = checker.getPropertyOfType( + const staticAccessValue = getStaticMemberAccessValue(node, context); + if (staticAccessValue == null) { + return; + } + const propertySymbol = getMemberIfExists( objType, - tsNode.name.text, + staticAccessValue, + checker, ); if (propertySymbol == null) { return; @@ -594,11 +599,20 @@ export default createRule({ continue; } + const staticAccessValue = getStaticMemberAccessValue( + node as NodeWithKey, + context, + ); + + if (staticAccessValue == null) { + continue; + } + for (const heritageType of heritageTypes) { checkHeritageTypeForMemberReturningVoid( nodeMember, heritageType, - memberName, + staticAccessValue, ); } } @@ -614,9 +628,13 @@ export default createRule({ function checkHeritageTypeForMemberReturningVoid( nodeMember: ts.Node, heritageType: ts.Type, - memberName: string, + staticAccessValue: string | symbol, ): void { - const heritageMember = getMemberIfExists(heritageType, memberName); + const heritageMember = getMemberIfExists( + heritageType, + staticAccessValue, + checker, + ); if (heritageMember == null) { return; } @@ -970,18 +988,45 @@ function getHeritageTypes( .map(typeExpression => checker.getTypeAtLocation(typeExpression)); } +function getWellKnownStringOfSymbol(symbol: symbol): string | null { + const globalSymbolKeys = Object.getOwnPropertyNames(Symbol); + + for (const key of globalSymbolKeys) { + if (symbol === Symbol[key as keyof typeof Symbol]) { + return key; + } + } + + return null; +} + /** - * @returns The member with the given name in `type`, if it exists. + * @returns The member with the given name or known-symbol in `type`, if it exists. */ function getMemberIfExists( type: ts.Type, - memberName: string, + staticAccessValue: string | symbol, + checker: ts.TypeChecker, ): ts.Symbol | undefined { - const escapedMemberName = ts.escapeLeadingUnderscores(memberName); - const symbolMemberMatch = type.getSymbol()?.members?.get(escapedMemberName); - return ( - symbolMemberMatch ?? tsutils.getPropertyOfType(type, escapedMemberName) - ); + if (typeof staticAccessValue === 'string') { + const escapedMemberName = ts.escapeLeadingUnderscores(staticAccessValue); + const symbolMemberMatch = type.getSymbol()?.members?.get(escapedMemberName); + return ( + symbolMemberMatch ?? tsutils.getPropertyOfType(type, escapedMemberName) + ); + } + + const wellKnownSymbolName = getWellKnownStringOfSymbol(staticAccessValue); + + if (wellKnownSymbolName != null) { + return tsutils.getWellKnownSymbolPropertyOfType( + type, + wellKnownSymbolName, + checker, + ); + } + + return undefined; } function isStaticMember(node: TSESTree.Node): boolean { diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index c75a000487f7..d67bbf0ea90c 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -234,13 +234,15 @@ function isParenlessArrowFunction( ); } -type NodeWithKey = +export type NodeWithKey = | TSESTree.MemberExpression | TSESTree.MethodDefinition | TSESTree.Property | TSESTree.PropertyDefinition | TSESTree.TSAbstractMethodDefinition - | TSESTree.TSAbstractPropertyDefinition; + | TSESTree.TSAbstractPropertyDefinition + | TSESTree.TSMethodSignature + | TSESTree.TSPropertySignature; /** * Gets a member being accessed or declared if its value can be determined statically, and 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 16ee46b5f01d..56824c676ab7 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-misused-promises'; import { getFixturesRootDir } from '../RuleTester'; @@ -1066,6 +1066,319 @@ declare const useCallback: unknown>( ) => T; useCallback(async () => {}); `, + { + code: ` +type O = { + 1: () => void; +}; + +const obj: O = { + 1() {}, +}; + `, + }, + { + code: ` +type O = { + 1: () => void; +}; + +const obj: O = { + [1]() {}, +}; + `, + }, + { + code: noFormat` +type O = { + stringLiteral: () => void; +}; + +const obj: O = { + 'stringLiteral'() {}, +}; + `, + }, + { + code: ` +type O = { + computedStringLiteral: () => void; +}; + +const obj: O = { + ['computedStringLiteral']() {}, +}; + `, + }, + { + code: ` +type O = { + [Symbol.iterator]: () => void; +}; + +const obj: O = { + [Symbol.iterator]() {}, +}; + `, + }, + { + code: ` +const staticSymbol = Symbol.for('static symbol'); + +type O = { + [staticSymbol]: () => void; +}; + +const obj: O = { + [staticSymbol]() {}, +}; + `, + }, + { + code: ` +type O = { + 1: () => Promise; +}; + +const obj: O = { + async 1() {}, +}; + `, + }, + { + code: ` +type O = { + 1: () => Promise; +}; + +const obj: O = { + async [1]() {}, +}; + `, + }, + { + code: noFormat` +type O = { + stringLiteral: () => Promise; +}; + +const obj: O = { + async 'stringLiteral'() {}, +}; + `, + }, + { + code: ` +type O = { + computedStringLiteral: () => Promise; +}; + +const obj: O = { + async ['computedStringLiteral']() {}, +}; + `, + }, + { + code: ` +type O = { + [Symbol.iterator]: () => Promise; +}; + +const obj: O = { + async [Symbol.iterator]() {}, +}; + `, + }, + { + code: ` +const staticSymbol = Symbol.for('static symbol'); + +type O = { + [staticSymbol]: () => Promise; +}; + +const obj: O = { + async [staticSymbol]() {}, +}; + `, + }, + { + code: ` +class MyClass { + 1(): void {} +} + +class MySubclass extends MyClass { + 1(): void {} +} + `, + }, + { + code: ` +class MyClass { + 1(): void {} +} + +class MySubclass extends MyClass { + [1](): void {} +} + `, + }, + { + code: noFormat` +class MyClass { + stringLiteral(): void {} +} + +class MySubclass extends MyClass { + 'stringLiteral'(): void {} +} + + `, + }, + { + code: ` +class MyClass { + computedStringLiteral(): void {} +} + +class MySubclass extends MyClass { + ['computedStringLiteral'](): void {} +} + `, + }, + { + code: ` +class MyClass { + [Symbol.iterator](): void {} +} + +class MySubclass extends MyClass { + [Symbol.iterator](): void {} +} + `, + }, + { + code: ` +const staticSymbol = Symbol.for('static symbol'); + +class MyClass { + [staticSymbol](): void {} +} + +class MySubclass extends MyClass { + [staticSymbol](): void {} +} + `, + }, + { + code: ` +interface MyInterface { + 1(): void; +} + +class MySubclass implements MyInterface { + 1(): void {} +} + `, + }, + { + code: ` +interface MyInterface { + 1(): void; +} + +class MySubclass implements MyInterface { + [1](): void {} +} + `, + }, + { + code: noFormat` +interface MyInterface { + stringLiteral(): void; +} + +class MySubclass implements MyInterface { + 'stringLiteral'(): void {} +} + + `, + }, + { + code: ` +interface MyInterface { + computedStringLiteral(): void; +} + +class MySubclass implements MyInterface { + ['computedStringLiteral'](): void {} +} + `, + }, + { + code: ` +interface MyInterface { + [Symbol.iterator](): void; +} + +class MySubclass implements MyInterface { + [Symbol.iterator](): void {} +} + `, + }, + { + code: ` +const staticSymbol = Symbol.for('static symbol'); + +interface MyInterface { + [staticSymbol](): void; +} + +class MySubclass implements MyInterface { + [staticSymbol](): void {} +} + `, + }, + { + code: ` +let a; + +type O = { + [a]: () => Promise; +}; + +const obj: O = { + async [a]() {}, +}; + `, + }, + { + code: ` +let a; + +interface MyInterface { + [a](): void; +} + +class MySubinterfaceExtendsMyInterface implements MyInterface { + [a]: () => Promise; +} + `, + }, + { + code: ` +const staticSymbol = Symbol(); + +interface MyInterface { + [staticSymbol](): Promise; +} + +class MySubclass implements MyInterface { + async [staticSymbol](): Promise {} +} + `, + }, ], invalid: [ @@ -2576,5 +2889,277 @@ const obj: O = { }, ], }, + { + code: ` +type O = { + 1: () => void; +}; + +const obj: O = { + async 1() {}, +}; + `, + errors: [ + { + line: 7, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { + 1: () => void; +}; + +const obj: O = { + async [1]() {}, +}; + `, + errors: [ + { + line: 7, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: noFormat` +type O = { + stringLiteral: () => void; +}; + +const obj: O = { + async 'stringLiteral'() {}, +}; + `, + errors: [ + { + line: 7, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { + computedStringLiteral: () => void; +}; + +const obj: O = { + async ['computedStringLiteral']() {}, +}; + `, + errors: [ + { + line: 7, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { + [Symbol.iterator]: () => void; +}; + +const obj: O = { + async [Symbol.iterator]() {}, +}; + `, + errors: [ + { + line: 7, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +class MyClass { + 1(): void {} +} + +class MySubclass extends MyClass { + async 1(): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +class MyClass { + 1(): void {} +} + +class MySubclass extends MyClass { + async [1](): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: noFormat` +class MyClass { + stringLiteral(): void {} +} + +class MySubclass extends MyClass { + async 'stringLiteral'(): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +class MyClass { + computedStringLiteral(): void {} +} + +class MySubclass extends MyClass { + async ['computedStringLiteral'](): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +class MyClass { + [Symbol.asyncIterator](): void {} +} + +class MySubclass extends MyClass { + async [Symbol.asyncIterator](): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +interface MyInterface { + 1(): void; +} + +interface MySubinterface extends MyInterface { + 1(): Promise; +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +interface MyInterface { + 1(): void; +} + +interface MySubinterface extends MyInterface { + [1](): Promise; +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +interface MyInterface { + stringLiteral(): void; +} + +interface MySubinterface extends MyInterface { + 'stringLiteral'(): Promise; +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +interface MyInterface { + computedStringLiteral(): void; +} + +interface MySubinterface extends MyInterface { + ['computedStringLiteral'](): Promise; +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +interface MyInterface { + [Symbol.asyncIterator](): void; +} + +interface MySubinterface extends MyInterface { + [Symbol.asyncIterator](): Promise; +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, + { + code: ` +class MyClass { + ''(): void {} +} + +class MySubclass extends MyClass { + async ''(): Promise {} +} + `, + errors: [ + { + line: 7, + messageId: 'voidReturnInheritedMethod', + }, + ], + }, ], });