From 9598b4dd4f0c26204b512eea7de8b89cb7bf9968 Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 14:47:36 +0900 Subject: [PATCH 01/16] refactor(eslint-plugin): create isVoidReturningFunctionType --- .../src/rules/no-misused-promises.ts | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 6e42e5474361..1eda89a97c42 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -219,7 +219,6 @@ function voidFunctionParams( node: ts.CallExpression | ts.NewExpression, ): Set { const voidReturnIndices = new Set(); - const thenableReturnIndices = new Set(); const type = checker.getTypeAtLocation(node.expression); for (const subType of tsutils.unionTypeParts(type)) { @@ -233,29 +232,37 @@ function voidFunctionParams( parameter, node.expression, ); - for (const subType of tsutils.unionTypeParts(type)) { - for (const signature of subType.getCallSignatures()) { - const returnType = signature.getReturnType(); - if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) { - voidReturnIndices.add(index); - } else if ( - tsutils.isThenableType(checker, node.expression, returnType) - ) { - thenableReturnIndices.add(index); - } - } + if (isVoidReturningFunctionType(checker, node.expression, type)) { + voidReturnIndices.add(index); } } } } + return voidReturnIndices; +} + +// Returns true if given type is a void-returning function. +function isVoidReturningFunctionType( + checker: ts.TypeChecker, + node: ts.Node, + type: ts.Type, +): boolean { + let hasVoidReturningFunction = false; + let hasThenableReturningFunction = false; + for (const subType of tsutils.unionTypeParts(type)) { + for (const signature of subType.getCallSignatures()) { + const returnType = signature.getReturnType(); + if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) { + hasVoidReturningFunction = true; + } else if (tsutils.isThenableType(checker, node, returnType)) { + hasThenableReturningFunction = true; + } + } + } // If a certain positional argument accepts both thenable and void returns, // a promise-returning function is valid - for (const thenable of thenableReturnIndices) { - voidReturnIndices.delete(thenable); - } - - return voidReturnIndices; + return hasVoidReturningFunction && !hasThenableReturningFunction; } // Returns true if the expression is a function that returns a thenable From 2d0f9d85db5cbe388f7176d9874ce88562fcd3bd Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 15:27:27 +0900 Subject: [PATCH 02/16] feat(eslint-plugin): add checkAssignments and checkVariableDeclaration --- .../src/rules/no-misused-promises.ts | 41 +++++++++++++++- .../tests/rules/no-misused-promises.test.ts | 49 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1eda89a97c42..d43c484e67fa 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -11,7 +11,9 @@ type Options = [ }, ]; -export default util.createRule({ +type MessageId = 'conditional' | 'voidReturn' | 'voidReturnVariable'; + +export default util.createRule({ name: 'no-misused-promises', meta: { docs: { @@ -22,6 +24,8 @@ export default util.createRule({ messages: { voidReturn: 'Promise returned in function argument where a void return was expected.', + voidReturnVariable: + 'Promise returned in variable where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ @@ -67,6 +71,8 @@ export default util.createRule({ const voidReturnChecks: TSESLint.RuleListener = { CallExpression: checkArguments, NewExpression: checkArguments, + AssignmentExpression: checkAssignments, + VariableDeclarator: checkVariableDeclaration, }; function checkTestConditional(node: { @@ -137,6 +143,39 @@ export default util.createRule({ } } + function checkAssignments(node: TSESTree.AssignmentExpression): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const varType = checker.getTypeAtLocation(tsNode.left); + if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) { + return; + } + + if (returnsThenable(checker, tsNode.right)) { + context.report({ + messageId: 'voidReturnVariable', + node: node.right, + }); + } + } + + function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (tsNode.initializer === undefined || node.init === null) { + return; + } + const varType = checker.getTypeAtLocation(tsNode.name); + if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) { + return; + } + + if (returnsThenable(checker, tsNode.initializer)) { + context.report({ + messageId: 'voidReturnVariable', + node: node.init, + }); + } + } + return { ...(checksConditionals ? conditionalChecks : {}), ...(checksVoidReturn ? voidReturnChecks : {}), 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 63e429000c03..df4d58fbe6f9 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -432,5 +432,54 @@ function test(p: Promise | undefined) { }, ], }, + { + code: ` +let f: () => void; +f = async () => { + return 3; +}; + `, + errors: [ + { + line: 3, + messageId: 'voidReturnVariable', + }, + ], + }, + { + 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', + }, + ], + }, ], }); From 96ae88d2a0fc28dd97cd19add74c1855398706bd Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 15:30:42 +0900 Subject: [PATCH 03/16] test(eslint-plugin): add valid test cases --- .../tests/rules/no-misused-promises.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 df4d58fbe6f9..2ed04f853bf8 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -166,6 +166,16 @@ async function test(p: Promise | undefined) { } } `, + ` +let f; +f = async () => 10; + `, + ` +let f: () => Promise; +f = async () => 10; +const g = async () => 0; +const h: () => Promise = async () => 10; + `, ], invalid: [ From 5d39edd45332e2874ff3db3edd86f687b108bad2 Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 16:46:20 +0900 Subject: [PATCH 04/16] feat(eslint-plugin): add object property checks --- .../src/rules/no-misused-promises.ts | 93 ++++++++++++++++++- .../tests/rules/no-misused-promises.test.ts | 92 ++++++++++++++++++ 2 files changed, 180 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index d43c484e67fa..1821a63c5456 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -11,7 +11,11 @@ type Options = [ }, ]; -type MessageId = 'conditional' | 'voidReturn' | 'voidReturnVariable'; +type MessageId = + | 'conditional' + | 'voidReturn' + | 'voidReturnVariable' + | 'voidReturnProperty'; export default util.createRule({ name: 'no-misused-promises', @@ -26,6 +30,8 @@ export default util.createRule({ 'Promise returned in function argument where a void return was expected.', voidReturnVariable: 'Promise returned in variable where a void return was expected.', + voidReturnProperty: + 'Promise returned in property where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ @@ -73,6 +79,7 @@ export default util.createRule({ NewExpression: checkArguments, AssignmentExpression: checkAssignments, VariableDeclarator: checkVariableDeclaration, + Property: checkProperties, }; function checkTestConditional(node: { @@ -176,6 +183,85 @@ export default util.createRule({ } } + function checkProperties(node: TSESTree.Property): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (ts.isPropertyAssignment(tsNode)) { + const contextualType = checker.getContextualType(tsNode.initializer); + if (contextualType === undefined) { + return; + } + if ( + !isVoidReturningFunctionType( + checker, + tsNode.initializer, + contextualType, + ) + ) { + return; + } + if (returnsThenable(checker, tsNode.initializer)) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + } else if (ts.isShorthandPropertyAssignment(tsNode)) { + const contextualType = checker.getContextualType(tsNode.name); + if (contextualType === undefined) { + return; + } + if ( + !isVoidReturningFunctionType(checker, tsNode.name, contextualType) + ) { + return; + } + if (returnsThenable(checker, tsNode.name)) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + } else if (ts.isMethodDeclaration(tsNode)) { + if (ts.isComputedPropertyName(tsNode.name)) { + return; + } + const obj = tsNode.parent; + if (!ts.isObjectLiteralExpression(obj)) { + return; + } + const objType = + checker.getContextualType(obj) ?? checker.getTypeAtLocation(obj); + const propertySymbol = checker.getPropertyOfType( + objType, + tsNode.name.text, + ); + if (propertySymbol === undefined) { + return; + } + + const contextualType = checker.getTypeOfSymbolAtLocation( + propertySymbol, + tsNode.name, + ); + + if (contextualType === undefined) { + return; + } + if ( + !isVoidReturningFunctionType(checker, tsNode.name, contextualType) + ) { + return; + } + if (returnsThenable(checker, tsNode)) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + return; + } + } + return { ...(checksConditionals ? conditionalChecks : {}), ...(checksVoidReturn ? voidReturnChecks : {}), @@ -305,10 +391,7 @@ function isVoidReturningFunctionType( } // Returns true if the expression is a function that returns a thenable -function returnsThenable( - checker: ts.TypeChecker, - node: ts.Expression, -): boolean { +function returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean { const type = checker.getApparentType(checker.getTypeAtLocation(node)); for (const subType of tsutils.unionTypeParts(type)) { 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 2ed04f853bf8..61e161da548f 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -176,6 +176,24 @@ f = async () => 10; const g = async () => 0; const h: () => Promise = async () => 10; `, + ` +const obj = { + f: async () => 10, +}; + `, + ` +const f = async () => 123; +const obj = { + f, +}; + `, + ` +const obj = { + async f() { + return 0; + }, +}; + `, ], invalid: [ @@ -491,5 +509,79 @@ obj.f = async () => { }, ], }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + f: async () => 'foo', +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + }, + { + 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', + }, + ], + }, ], }); From 18811b2167cd85ae0760edbb045a60a3b986fab9 Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 16:51:43 +0900 Subject: [PATCH 05/16] feat(eslint-plugin): add checkReturnStatements --- .../src/rules/no-misused-promises.ts | 28 ++++++++++++++++++- .../tests/rules/no-misused-promises.test.ts | 21 ++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1821a63c5456..0f012c9afc61 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -15,7 +15,8 @@ type MessageId = | 'conditional' | 'voidReturn' | 'voidReturnVariable' - | 'voidReturnProperty'; + | 'voidReturnProperty' + | 'voidReturnReturnValue'; export default util.createRule({ name: 'no-misused-promises', @@ -32,6 +33,8 @@ export default util.createRule({ 'Promise returned in variable where a void return was expected.', voidReturnProperty: 'Promise returned in property where a void return was expected.', + voidReturnReturnValue: + 'Promise returned in return value where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ @@ -80,6 +83,7 @@ export default util.createRule({ AssignmentExpression: checkAssignments, VariableDeclarator: checkVariableDeclaration, Property: checkProperties, + ReturnStatement: checkReturnStatements, }; function checkTestConditional(node: { @@ -262,6 +266,28 @@ export default util.createRule({ } } + function checkReturnStatements(node: TSESTree.ReturnStatement): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (tsNode.expression === undefined || node.argument === null) { + return; + } + const contextualType = checker.getContextualType(tsNode.expression); + if (contextualType === undefined) { + return; + } + if ( + !isVoidReturningFunctionType(checker, tsNode.expression, contextualType) + ) { + return; + } + if (returnsThenable(checker, tsNode.expression)) { + context.report({ + messageId: 'voidReturnReturnValue', + node: node.argument, + }); + } + } + return { ...(checksConditionals ? conditionalChecks : {}), ...(checksVoidReturn ? voidReturnChecks : {}), 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 61e161da548f..9468f798e902 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -194,6 +194,14 @@ const obj = { }, }; `, + ` +function f() { + return async () => 0; +} +function g() { + return; +} + `, ], invalid: [ @@ -583,5 +591,18 @@ function f(): O { }, ], }, + { + code: ` +function f(): () => void { + return async () => 0; +} + `, + errors: [ + { + line: 3, + messageId: 'voidReturnReturnValue', + }, + ], + }, ], }); From 733ad0fa33ec7a19076378fb48f259bcd539619c Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 17:20:28 +0900 Subject: [PATCH 06/16] feat(eslint-plugin): add checkJSXAttributes --- .../src/rules/no-misused-promises.ts | 33 ++++++++++++++++++- .../tests/rules/no-misused-promises.test.ts | 33 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 0f012c9afc61..20624f519cb2 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -16,7 +16,8 @@ type MessageId = | 'voidReturn' | 'voidReturnVariable' | 'voidReturnProperty' - | 'voidReturnReturnValue'; + | 'voidReturnReturnValue' + | 'voidReturnAttribute'; export default util.createRule({ name: 'no-misused-promises', @@ -35,6 +36,8 @@ export default util.createRule({ 'Promise returned in property where a void return was expected.', voidReturnReturnValue: 'Promise returned in return value where a void return was expected.', + voidReturnAttribute: + 'Promise returned in attribute where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ @@ -84,6 +87,7 @@ export default util.createRule({ VariableDeclarator: checkVariableDeclaration, Property: checkProperties, ReturnStatement: checkReturnStatements, + JSXAttribute: checkJSXAttributes, }; function checkTestConditional(node: { @@ -288,6 +292,33 @@ export default util.createRule({ } } + function checkJSXAttributes(node: TSESTree.JSXAttribute): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const value = tsNode.initializer; + if ( + node.value === null || + value === undefined || + !ts.isJsxExpression(value) || + value.expression === undefined + ) { + return; + } + const contextualType = checker.getContextualType(value); + if (contextualType === undefined) { + return; + } + if (!isVoidReturningFunctionType(checker, value, contextualType)) { + return; + } + + if (returnsThenable(checker, value.expression)) { + context.report({ + messageId: 'voidReturnAttribute', + node: node.value, + }); + } + } + return { ...(checksConditionals ? conditionalChecks : {}), ...(checksVoidReturn ? voidReturnChecks : {}), 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 9468f798e902..0fea7fded98c 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -604,5 +604,38 @@ function f(): () => void { }, ], }, + { + code: ` +type O = { + func: () => void; +}; +const Component = (obj: O) => null; + 0} />; + `, + filename: 'react.tsx', + errors: [ + { + line: 6, + messageId: 'voidReturnAttribute', + }, + ], + }, + { + code: ` +type O = { + func: () => void; +}; +const g = async () => 'foo'; +const Component = (obj: O) => null; +; + `, + filename: 'react.tsx', + errors: [ + { + line: 7, + messageId: 'voidReturnAttribute', + }, + ], + }, ], }); From 3585e63399946e006c18ec033a6685b28ae2d9d0 Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 17:50:08 +0900 Subject: [PATCH 07/16] fix(website): resolve newly introduced lint errors --- .../website/src/components/OptionsSelector.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/website/src/components/OptionsSelector.tsx b/packages/website/src/components/OptionsSelector.tsx index 83715fc3ae5c..86c842965073 100644 --- a/packages/website/src/components/OptionsSelector.tsx +++ b/packages/website/src/components/OptionsSelector.tsx @@ -74,17 +74,21 @@ function OptionsSelector({ [setState], ); - const copyLinkToClipboard = useCallback(async () => { - await navigator.clipboard.writeText(document.location.toString()); - setCopyLink(true); + const copyLinkToClipboard = useCallback(() => { + void navigator.clipboard + .writeText(document.location.toString()) + .then(() => { + setCopyLink(true); + }); }, []); - const copyMarkdownToClipboard = useCallback(async () => { + const copyMarkdownToClipboard = useCallback(() => { if (isLoading) { return; } - await navigator.clipboard.writeText(createMarkdown(state)); - setCopyMarkdown(true); + void navigator.clipboard.writeText(createMarkdown(state)).then(() => { + setCopyMarkdown(true); + }); }, [state, isLoading]); const openIssue = useCallback(() => { From 88fff213242256263c77766bcdc43fbf15a7a40c Mon Sep 17 00:00:00 2001 From: uhyo Date: Sat, 12 Feb 2022 22:27:27 +0900 Subject: [PATCH 08/16] test(eslint-plugin): worship code coverage --- .../src/rules/no-misused-promises.ts | 3 -- .../tests/rules/no-misused-promises.test.ts | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 20624f519cb2..27d83c041277 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -252,9 +252,6 @@ export default util.createRule({ tsNode.name, ); - if (contextualType === undefined) { - return; - } if ( !isVoidReturningFunctionType(checker, tsNode.name, contextualType) ) { 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 0fea7fded98c..afe14590aca5 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -195,6 +195,31 @@ const obj = { }; `, ` +type O = { f: () => Promise; g: () => Promise }; +const g = async () => 0; +const obj: O = { + f: async () => 10, + g, +}; + `, + ` +type O = { f: () => Promise }; +const name = 'f'; +const obj: O = { + async [name]() { + return 10; + }, +}; + `, + ` +const obj = { + f: async () => 'foo', + async g() { + return 0; + }, +}; + `, + ` function f() { return async () => 0; } @@ -202,6 +227,24 @@ function g() { return; } `, + { + code: ` +type O = { + bool: boolean; + func: () => Promise; +}; +const Component = (obj: O) => null; + 10} />; + `, + filename: 'react.tsx', + }, + { + code: ` +const Component: any = () => null; + 10} />; + `, + filename: 'react.tsx', + }, ], invalid: [ From e82b964ac922b71075d3ed00e62a77e739eb1f52 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:42:45 +0900 Subject: [PATCH 09/16] Apply suggestions from code review Co-authored-by: Josh Goldberg --- packages/eslint-plugin/src/rules/no-misused-promises.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 27d83c041277..9aa411087843 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -31,7 +31,7 @@ export default util.createRule({ voidReturn: 'Promise returned in function argument where a void return was expected.', voidReturnVariable: - 'Promise returned in variable where a void return was expected.', + 'Promise-returning function provided to variable where a void return was expected.', voidReturnProperty: 'Promise returned in property where a void return was expected.', voidReturnReturnValue: @@ -158,7 +158,7 @@ export default util.createRule({ } } - function checkAssignments(node: TSESTree.AssignmentExpression): void { + function checkAssignment(node: TSESTree.AssignmentExpression): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const varType = checker.getTypeAtLocation(tsNode.left); if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) { From 9c4bcfd8e954116c0e9cb8de80a0dd25927327a4 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:46:05 +0900 Subject: [PATCH 10/16] fix(eslint-plugin): rename checker functions to singular --- .../eslint-plugin/src/rules/no-misused-promises.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 9aa411087843..f11980b32868 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -83,11 +83,11 @@ export default util.createRule({ const voidReturnChecks: TSESLint.RuleListener = { CallExpression: checkArguments, NewExpression: checkArguments, - AssignmentExpression: checkAssignments, + AssignmentExpression: checkAssignment, VariableDeclarator: checkVariableDeclaration, - Property: checkProperties, - ReturnStatement: checkReturnStatements, - JSXAttribute: checkJSXAttributes, + Property: checkProperty, + ReturnStatement: checkReturnStatement, + JSXAttribute: checkJSXAttribute, }; function checkTestConditional(node: { @@ -191,7 +191,7 @@ export default util.createRule({ } } - function checkProperties(node: TSESTree.Property): void { + function checkProperty(node: TSESTree.Property): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); if (ts.isPropertyAssignment(tsNode)) { const contextualType = checker.getContextualType(tsNode.initializer); @@ -267,7 +267,7 @@ export default util.createRule({ } } - function checkReturnStatements(node: TSESTree.ReturnStatement): void { + function checkReturnStatement(node: TSESTree.ReturnStatement): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); if (tsNode.expression === undefined || node.argument === null) { return; @@ -289,7 +289,7 @@ export default util.createRule({ } } - function checkJSXAttributes(node: TSESTree.JSXAttribute): void { + function checkJSXAttribute(node: TSESTree.JSXAttribute): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const value = tsNode.initializer; if ( From dc452e359f6acbca6848390a86466f6d1befab10 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:47:37 +0900 Subject: [PATCH 11/16] fix(eslint-plugin): align error messages --- packages/eslint-plugin/src/rules/no-misused-promises.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index f11980b32868..79a4ee5e0841 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -33,11 +33,11 @@ export default util.createRule({ voidReturnVariable: 'Promise-returning function provided to variable where a void return was expected.', voidReturnProperty: - 'Promise returned in property where a void return was expected.', + 'Promise-returning function provided to property where a void return was expected.', voidReturnReturnValue: - 'Promise returned in return value where a void return was expected.', + 'Promise-returning function provided to return value where a void return was expected.', voidReturnAttribute: - 'Promise returned in attribute where a void return was expected.', + 'Promise-returning function provided to attribute where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ From b59abc494a764c5379b8472b383a5e157b1f0e49 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:51:21 +0900 Subject: [PATCH 12/16] refactor(eslint-plugin): change MessageId --- .../eslint-plugin/src/rules/no-misused-promises.ts | 6 +++--- .../tests/rules/no-misused-promises.test.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 79a4ee5e0841..1983e6313b80 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -13,7 +13,7 @@ type Options = [ type MessageId = | 'conditional' - | 'voidReturn' + | 'voidReturnArgument' | 'voidReturnVariable' | 'voidReturnProperty' | 'voidReturnReturnValue' @@ -28,7 +28,7 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - voidReturn: + 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.', @@ -151,7 +151,7 @@ export default util.createRule({ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument); if (returnsThenable(checker, tsNode as ts.Expression)) { context.report({ - messageId: 'voidReturn', + messageId: 'voidReturnArgument', node: argument, }); } 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 afe14590aca5..97b7905203e9 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -344,7 +344,7 @@ if (!Promise.resolve()) { errors: [ { line: 2, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -358,7 +358,7 @@ new Promise(async (resolve, reject) => { errors: [ { line: 2, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -375,7 +375,7 @@ fnWithCallback('val', async (err, res) => { errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -390,7 +390,7 @@ fnWithCallback('val', (err, res) => Promise.resolve(res)); errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -411,7 +411,7 @@ fnWithCallback('val', (err, res) => { errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -428,7 +428,7 @@ fnWithCallback?.('val', (err, res) => Promise.resolve(res)); errors: [ { line: 8, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -451,7 +451,7 @@ fnWithCallback('val', (err, res) => { errors: [ { line: 8, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, From deae75592cd2a61bb396350e751474210a2f8bd8 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:55:38 +0900 Subject: [PATCH 13/16] refactor(eslint-plugin): fix if statements style --- .../src/rules/no-misused-promises.ts | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1983e6313b80..2ac07a438432 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -195,19 +195,15 @@ export default util.createRule({ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); if (ts.isPropertyAssignment(tsNode)) { const contextualType = checker.getContextualType(tsNode.initializer); - if (contextualType === undefined) { - return; - } if ( - !isVoidReturningFunctionType( + contextualType !== undefined && + isVoidReturningFunctionType( checker, tsNode.initializer, contextualType, - ) + ) && + returnsThenable(checker, tsNode.initializer) ) { - return; - } - if (returnsThenable(checker, tsNode.initializer)) { context.report({ messageId: 'voidReturnProperty', node: node.value, @@ -215,15 +211,11 @@ export default util.createRule({ } } else if (ts.isShorthandPropertyAssignment(tsNode)) { const contextualType = checker.getContextualType(tsNode.name); - if (contextualType === undefined) { - return; - } if ( - !isVoidReturningFunctionType(checker, tsNode.name, contextualType) + contextualType !== undefined && + isVoidReturningFunctionType(checker, tsNode.name, contextualType) && + returnsThenable(checker, tsNode.name) ) { - return; - } - if (returnsThenable(checker, tsNode.name)) { context.report({ messageId: 'voidReturnProperty', node: node.value, @@ -253,11 +245,9 @@ export default util.createRule({ ); if ( - !isVoidReturningFunctionType(checker, tsNode.name, contextualType) + isVoidReturningFunctionType(checker, tsNode.name, contextualType) && + returnsThenable(checker, tsNode) ) { - return; - } - if (returnsThenable(checker, tsNode)) { context.report({ messageId: 'voidReturnProperty', node: node.value, @@ -273,15 +263,15 @@ export default util.createRule({ return; } const contextualType = checker.getContextualType(tsNode.expression); - if (contextualType === undefined) { - return; - } if ( - !isVoidReturningFunctionType(checker, tsNode.expression, contextualType) + contextualType !== undefined && + isVoidReturningFunctionType( + checker, + tsNode.expression, + contextualType, + ) && + returnsThenable(checker, tsNode.expression) ) { - return; - } - if (returnsThenable(checker, tsNode.expression)) { context.report({ messageId: 'voidReturnReturnValue', node: node.argument, @@ -301,14 +291,11 @@ export default util.createRule({ return; } const contextualType = checker.getContextualType(value); - if (contextualType === undefined) { - return; - } - if (!isVoidReturningFunctionType(checker, value, contextualType)) { - return; - } - - if (returnsThenable(checker, value.expression)) { + if ( + contextualType !== undefined && + isVoidReturningFunctionType(checker, value, contextualType) && + returnsThenable(checker, value.expression) + ) { context.report({ messageId: 'voidReturnAttribute', node: node.value, From 691cc2c228fb6497a2e9962156a0247e5291a531 Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 09:59:41 +0900 Subject: [PATCH 14/16] refactor(eslint-plugin): no need of getTypeAtLocation --- packages/eslint-plugin/src/rules/no-misused-promises.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 2ac07a438432..ebdb1e6eee95 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -229,8 +229,10 @@ export default util.createRule({ if (!ts.isObjectLiteralExpression(obj)) { return; } - const objType = - checker.getContextualType(obj) ?? checker.getTypeAtLocation(obj); + const objType = checker.getContextualType(obj); + if (objType === undefined) { + return; + } const propertySymbol = checker.getPropertyOfType( objType, tsNode.name.text, From f277f831ae59514d52f1dd2cf55fe893ae4a427a Mon Sep 17 00:00:00 2001 From: uhyo Date: Thu, 24 Feb 2022 10:29:04 +0900 Subject: [PATCH 15/16] refactor(eslint-plugin): make coverage 100% --- packages/eslint-plugin/src/rules/no-misused-promises.ts | 5 ++++- .../eslint-plugin/tests/rules/no-misused-promises.test.ts | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index ebdb1e6eee95..d199ca120bb7 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -226,7 +226,10 @@ export default util.createRule({ return; } const obj = tsNode.parent; - if (!ts.isObjectLiteralExpression(obj)) { + + // This condition isn't satisfied but needed for type checking. + // The fundamental reason is that mapping between ESLint and TypeScript ASTs is not 1:1. + /* istanbul ignore if */ if (!ts.isObjectLiteralExpression(obj)) { return; } const objType = checker.getContextualType(obj); 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 97b7905203e9..961a21d4c892 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -212,6 +212,13 @@ const obj: O = { }; `, ` +const obj: number = { + g() { + return 10; + }, +}; + `, + ` const obj = { f: async () => 'foo', async g() { From eadaaa8704449d7e3aff3fb0303f7acb18c2a45e Mon Sep 17 00:00:00 2001 From: uhyo Date: Fri, 25 Feb 2022 01:04:05 +0900 Subject: [PATCH 16/16] refactor(eslint-plugin): update comment --- .../eslint-plugin/src/rules/no-misused-promises.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index d199ca120bb7..7f08402334cc 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -227,11 +227,16 @@ export default util.createRule({ } const obj = tsNode.parent; - // This condition isn't satisfied but needed for type checking. - // The fundamental reason is that mapping between ESLint and TypeScript ASTs is not 1:1. - /* istanbul ignore if */ if (!ts.isObjectLiteralExpression(obj)) { + // Below condition isn't satisfied unless something goes wrong, + // but is needed for type checking. + // 'node' does not include class method declaration so 'obj' is + // always an object literal expression, but after converting 'node' + // to TypeScript AST, its type includes MethodDeclaration which + // does include the case of class method declaration. + if (!ts.isObjectLiteralExpression(obj)) { return; } + const objType = checker.getContextualType(obj); if (objType === undefined) { return; 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