Content-Length: 17746 | pFad | http://github.com/typescript-eslint/typescript-eslint/pull/4541.diff
thub.com
diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts
index 6e42e5474361..7f08402334cc 100644
--- a/packages/eslint-plugin/src/rules/no-misused-promises.ts
+++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts
@@ -11,7 +11,15 @@ type Options = [
},
];
-export default util.createRule({
+type MessageId =
+ | 'conditional'
+ | 'voidReturnArgument'
+ | 'voidReturnVariable'
+ | 'voidReturnProperty'
+ | 'voidReturnReturnValue'
+ | 'voidReturnAttribute';
+
+export default util.createRule({
name: 'no-misused-promises',
meta: {
docs: {
@@ -20,8 +28,16 @@ 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.',
+ 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.',
conditional: 'Expected non-Promise value in a boolean conditional.',
},
schema: [
@@ -67,6 +83,11 @@ export default util.createRule({
const voidReturnChecks: TSESLint.RuleListener = {
CallExpression: checkArguments,
NewExpression: checkArguments,
+ AssignmentExpression: checkAssignment,
+ VariableDeclarator: checkVariableDeclaration,
+ Property: checkProperty,
+ ReturnStatement: checkReturnStatement,
+ JSXAttribute: checkJSXAttribute,
};
function checkTestConditional(node: {
@@ -130,13 +151,168 @@ 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,
});
}
}
}
+ function checkAssignment(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,
+ });
+ }
+ }
+
+ function checkProperty(node: TSESTree.Property): void {
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
+ if (ts.isPropertyAssignment(tsNode)) {
+ const contextualType = checker.getContextualType(tsNode.initializer);
+ if (
+ contextualType !== undefined &&
+ isVoidReturningFunctionType(
+ checker,
+ tsNode.initializer,
+ contextualType,
+ ) &&
+ 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 &&
+ isVoidReturningFunctionType(checker, tsNode.name, contextualType) &&
+ 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;
+
+ // 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;
+ }
+ const propertySymbol = checker.getPropertyOfType(
+ objType,
+ tsNode.name.text,
+ );
+ if (propertySymbol === undefined) {
+ return;
+ }
+
+ const contextualType = checker.getTypeOfSymbolAtLocation(
+ propertySymbol,
+ tsNode.name,
+ );
+
+ if (
+ isVoidReturningFunctionType(checker, tsNode.name, contextualType) &&
+ returnsThenable(checker, tsNode)
+ ) {
+ context.report({
+ messageId: 'voidReturnProperty',
+ node: node.value,
+ });
+ }
+ return;
+ }
+ }
+
+ function checkReturnStatement(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 &&
+ isVoidReturningFunctionType(
+ checker,
+ tsNode.expression,
+ contextualType,
+ ) &&
+ returnsThenable(checker, tsNode.expression)
+ ) {
+ context.report({
+ messageId: 'voidReturnReturnValue',
+ node: node.argument,
+ });
+ }
+ }
+
+ function checkJSXAttribute(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 &&
+ isVoidReturningFunctionType(checker, value, contextualType) &&
+ returnsThenable(checker, value.expression)
+ ) {
+ context.report({
+ messageId: 'voidReturnAttribute',
+ node: node.value,
+ });
+ }
+ }
+
return {
...(checksConditionals ? conditionalChecks : {}),
...(checksVoidReturn ? voidReturnChecks : {}),
@@ -219,7 +395,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,36 +408,41 @@ 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);
}
}
}
}
- // 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;
}
-// Returns true if the expression is a function that returns a thenable
-function returnsThenable(
+// Returns true if given type is a void-returning function.
+function isVoidReturningFunctionType(
checker: ts.TypeChecker,
- node: ts.Expression,
+ 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
+ return hasVoidReturningFunction && !hasThenableReturningFunction;
+}
+
+// Returns true if the expression is a function that returns a thenable
+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 63e429000c03..961a21d4c892 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,92 @@ 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;
+ `,
+ `
+const obj = {
+ f: async () => 10,
+};
+ `,
+ `
+const f = async () => 123;
+const obj = {
+ f,
+};
+ `,
+ `
+const obj = {
+ async f() {
+ return 0;
+ },
+};
+ `,
+ `
+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: number = {
+ g() {
+ return 10;
+ },
+};
+ `,
+ `
+const obj = {
+ f: async () => 'foo',
+ async g() {
+ return 0;
+ },
+};
+ `,
+ `
+function f() {
+ return async () => 0;
+}
+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: [
@@ -265,7 +351,7 @@ if (!Promise.resolve()) {
errors: [
{
line: 2,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -279,7 +365,7 @@ new Promise(async (resolve, reject) => {
errors: [
{
line: 2,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -296,7 +382,7 @@ fnWithCallback('val', async (err, res) => {
errors: [
{
line: 6,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -311,7 +397,7 @@ fnWithCallback('val', (err, res) => Promise.resolve(res));
errors: [
{
line: 6,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -332,7 +418,7 @@ fnWithCallback('val', (err, res) => {
errors: [
{
line: 6,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -349,7 +435,7 @@ fnWithCallback?.('val', (err, res) => Promise.resolve(res));
errors: [
{
line: 8,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -372,7 +458,7 @@ fnWithCallback('val', (err, res) => {
errors: [
{
line: 8,
- messageId: 'voidReturn',
+ messageId: 'voidReturnArgument',
},
],
},
@@ -432,5 +518,174 @@ 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',
+ },
+ ],
+ },
+ {
+ 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',
+ },
+ ],
+ },
+ {
+ code: `
+function f(): () => void {
+ return async () => 0;
+}
+ `,
+ errors: [
+ {
+ line: 3,
+ messageId: 'voidReturnReturnValue',
+ },
+ ],
+ },
+ {
+ 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',
+ },
+ ],
+ },
],
});
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(() => {
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/typescript-eslint/typescript-eslint/pull/4541.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy