Skip to content

Commit

Permalink
feat(eslint-plugin) [no-floating-promises] Error on logical expression
Browse files Browse the repository at this point in the history
  • Loading branch information
islandryu committed Jan 20, 2023
1 parent eefc578 commit 7c3f94e
Show file tree
Hide file tree
Showing 2 changed files with 335 additions and 11 deletions.
36 changes: 25 additions & 11 deletions packages/eslint-plugin/src/rules/no-floating-promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as tsutils from 'tsutils';
import * as ts from 'typescript';

import * as util from '../util';
import { OperatorPrecedence } from '../util';

type Options = [
{
Expand Down Expand Up @@ -88,10 +89,21 @@ export default util.createRule<Options, MessageId>({
suggest: [
{
messageId: 'floatingFixVoid',
fix(fixer): TSESLint.RuleFix {
let code = sourceCode.getText(node);
code = `void ${code}`;
return fixer.replaceText(node, code);
fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(
node.expression,
);
if (isHigherPrecedenceThanUnary(tsNode)) {
return fixer.insertTextBefore(node, 'void ');
} else {
return [
fixer.insertTextBefore(node, 'void ('),
fixer.insertTextAfterRange(
[expression.range[1], expression.range[1]],
')',
),
];
}
},
},
],
Expand All @@ -116,7 +128,7 @@ export default util.createRule<Options, MessageId>({
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(
node.expression,
);
if (isHigherPrecedenceThanAwait(tsNode)) {
if (isHigherPrecedenceThanUnary(tsNode)) {
return fixer.insertTextBefore(node, 'await ');
} else {
return [
Expand All @@ -136,16 +148,13 @@ export default util.createRule<Options, MessageId>({
},
};

function isHigherPrecedenceThanAwait(node: ts.Node): boolean {
function isHigherPrecedenceThanUnary(node: ts.Node): boolean {
const operator = tsutils.isBinaryExpression(node)
? node.operatorToken.kind
: ts.SyntaxKind.Unknown;
const nodePrecedence = util.getOperatorPrecedence(node.kind, operator);
const awaitPrecedence = util.getOperatorPrecedence(
ts.SyntaxKind.AwaitExpression,
ts.SyntaxKind.Unknown,
);
return nodePrecedence > awaitPrecedence;
const unaryPrecedence = OperatorPrecedence.Unary;
return nodePrecedence > unaryPrecedence;
}

function isAsyncIife(node: TSESTree.ExpressionStatement): boolean {
Expand Down Expand Up @@ -214,6 +223,11 @@ export default util.createRule<Options, MessageId>({
// `new Promise()`), the promise is not handled because it doesn't have the
// necessary then/catch call at the end of the chain.
return true;
} else if (node.type === AST_NODE_TYPES.LogicalExpression) {
return (
isUnhandledPromise(checker, node.left) ||
isUnhandledPromise(checker, node.right)
);
}

// We conservatively return false for all other types of expressions because
Expand Down
310 changes: 310 additions & 0 deletions packages/eslint-plugin/tests/rules/no-floating-promises.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,57 @@ void doSomething();
`,
options: [{ ignoreIIFE: true }],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
void (condition && myPromise());
}
`,
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
await (condition && myPromise());
}
`,
options: [{ ignoreVoid: false }],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
condition && void myPromise();
}
`,
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
condition && (await myPromise());
}
`,
options: [{ ignoreVoid: false }],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
let condition = false;
condition && myPromise();
condition = true;
condition || myPromise();
condition ?? myPromise();
}
`,
options: [{ ignoreVoid: false }],
},
],

invalid: [
Expand Down Expand Up @@ -1117,5 +1168,264 @@ async function test() {
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
void condition || myPromise();
}
`,
errors: [
{
line: 6,
messageId: 'floatingVoid',
suggestions: [
{
messageId: 'floatingFixVoid',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
void (void condition || myPromise());
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
(await condition) && myPromise();
}
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 6,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
await ((await condition) && myPromise());
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
condition && myPromise();
}
`,
errors: [
{
line: 6,
messageId: 'floatingVoid',
suggestions: [
{
messageId: 'floatingFixVoid',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = true;
void (condition && myPromise());
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = false;
condition || myPromise();
}
`,
errors: [
{
line: 6,
messageId: 'floatingVoid',
suggestions: [
{
messageId: 'floatingFixVoid',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = false;
void (condition || myPromise());
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = null;
condition ?? myPromise();
}
`,
errors: [
{
line: 6,
messageId: 'floatingVoid',
suggestions: [
{
messageId: 'floatingFixVoid',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = null;
void (condition ?? myPromise());
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = true;
condition && myPromise;
}
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = true;
await (condition && myPromise);
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = false;
condition || myPromise;
}
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = false;
await (condition || myPromise);
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = null;
condition ?? myPromise;
}
`,
options: [{ ignoreVoid: false }],
errors: [
{
line: 5,
messageId: 'floating',
suggestions: [
{
messageId: 'floatingFixAwait',
output: `
async function foo() {
const myPromise = Promise.resolve(true);
let condition = null;
await (condition ?? myPromise);
}
`,
},
],
},
],
},
{
code: `
async function foo() {
const myPromise = async () => void 0;
const condition = false;
condition || condition || myPromise();
}
`,
errors: [
{
line: 6,
messageId: 'floatingVoid',
suggestions: [
{
messageId: 'floatingFixVoid',
output: `
async function foo() {
const myPromise = async () => void 0;
const condition = false;
void (condition || condition || myPromise());
}
`,
},
],
},
],
},
],
});

0 comments on commit 7c3f94e

Please sign in to comment.