Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no-single-promise-in-promise-methods: Remove broken autofix for Promise.all() #2386

Merged
23 changes: 18 additions & 5 deletions rules/no-single-promise-in-promise-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
const {
isCommaToken,
} = require('@eslint-community/eslint-utils');
const {isMethodCall} = require('./ast/index.js');
const {
isMethodCall,
isExpressionStatement,
} = require('./ast/index.js');
const {
getParenthesizedText,
isParenthesized,
Expand Down Expand Up @@ -77,7 +80,7 @@ const unwrapNonAwaitedCallExpression = (callExpression, sourceCode) => fixer =>
const switchToPromiseResolve = (callExpression, sourceCode) => function * (fixer) {
/*
```
Promise.all([promise,])
Promise.race([promise,])
// ^^^ methodNameNode
```
*/
Expand All @@ -87,14 +90,14 @@ const switchToPromiseResolve = (callExpression, sourceCode) => function * (fixer
const [arrayExpression] = callExpression.arguments;
/*
```
Promise.all([promise,])
Promise.race([promise,])
// ^ openingBracketToken
```
*/
const openingBracketToken = sourceCode.getFirstToken(arrayExpression);
/*
```
Promise.all([promise,])
Promise.race([promise,])
// ^ penultimateToken
// ^ closingBracketToken
```
Expand All @@ -119,11 +122,13 @@ const create = context => ({
return;
}

const methodName = callExpression.callee.property.name;

const problem = {
node: callExpression.arguments[0],
messageId: MESSAGE_ID_ERROR,
data: {
method: callExpression.callee.property.name,
method: methodName,
},
};

Expand All @@ -132,11 +137,19 @@ const create = context => ({
if (
callExpression.parent.type === 'AwaitExpression'
&& callExpression.parent.argument === callExpression
&& (
methodName !== 'all'
|| isExpressionStatement(callExpression.parent.parent)
)
) {
problem.fix = unwrapAwaitedCallExpression(callExpression, sourceCode);
return problem;
}

if (methodName === 'all') {
return problem;
}

problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION_UNWRAP,
Expand Down
150 changes: 87 additions & 63 deletions test/no-single-promise-in-promise-methods.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,41 @@ test.snapshot({
valid: [
],
invalid: [
'await Promise.all([(0, promise)])',
'async function * foo() {await Promise.all([yield promise])}',
'async function * foo() {await Promise.all([yield* promise])}',
'await Promise.all([() => promise,],)',
'await Promise.all([a ? b : c,],)',
'await Promise.all([x ??= y,],)',
'await Promise.all([x ||= y,],)',
'await Promise.all([x &&= y,],)',
'await Promise.all([x |= y,],)',
'await Promise.all([x ^= y,],)',
'await Promise.all([x ??= y,],)',
'await Promise.all([x ||= y,],)',
'await Promise.all([x &&= y,],)',
'await Promise.all([x | y,],)',
'await Promise.all([x ^ y,],)',
'await Promise.all([x & y,],)',
'await Promise.all([x !== y,],)',
'await Promise.all([x == y,],)',
'await Promise.all([x in y,],)',
'await Promise.all([x >>> y,],)',
'await Promise.all([x + y,],)',
'await Promise.all([x / y,],)',
'await Promise.all([x ** y,],)',
'await Promise.all([promise,],)',
'await Promise.all([getPromise(),],)',
'await Promise.all([promises[0],],)',
'await Promise.all([await promise])',
'await Promise.race([(0, promise)])',
'async function * foo() {await Promise.race([yield promise])}',
'async function * foo() {await Promise.race([yield* promise])}',
'await Promise.race([() => promise,],)',
'await Promise.race([a ? b : c,],)',
'await Promise.race([x ??= y,],)',
'await Promise.race([x ||= y,],)',
'await Promise.race([x &&= y,],)',
'await Promise.race([x |= y,],)',
'await Promise.race([x ^= y,],)',
'await Promise.race([x ??= y,],)',
'await Promise.race([x ||= y,],)',
'await Promise.race([x &&= y,],)',
'await Promise.race([x | y,],)',
'await Promise.race([x ^ y,],)',
'await Promise.race([x & y,],)',
'await Promise.race([x !== y,],)',
'await Promise.race([x == y,],)',
'await Promise.race([x in y,],)',
'await Promise.race([x >>> y,],)',
'await Promise.race([x + y,],)',
'await Promise.race([x / y,],)',
'await Promise.race([x ** y,],)',
'await Promise.race([promise,],)',
'await Promise.race([getPromise(),],)',
'await Promise.race([promises[0],],)',
'await Promise.race([await promise])',
'await Promise.any([promise])',
'await Promise.race([promise])',
'await Promise.all([new Promise(() => {})])',
'+await Promise.all([+1])',
'await Promise.race([new Promise(() => {})])',
'+await Promise.race([+1])',

// ASI, `Promise.all()` is not really `await`ed
// ASI, `Promise.race()` is not really `await`ed
outdent`
await Promise.all([(x,y)])
await Promise.race([(x,y)])
[0].toString()
`,
],
Expand All @@ -51,54 +51,78 @@ test.snapshot({
// Not `await`ed
test.snapshot({
valid: [
'Promise.all([promise, anotherPromise])',
'Promise.all(notArrayLiteral)',
'Promise.all([...promises])',
'Promise.race([promise, anotherPromise])',
'Promise.race(notArrayLiteral)',
'Promise.race([...promises])',
'Promise.any([promise, anotherPromise])',
'Promise.race([promise, anotherPromise])',
'Promise.notListedMethod([promise])',
'Promise[all]([promise])',
'Promise.all([,])',
'NotPromise.all([promise])',
'Promise?.all([promise])',
'Promise.all?.([promise])',
'Promise.all(...[promise])',
'Promise.all([promise], extraArguments)',
'Promise.all()',
'new Promise.all([promise])',
'Promise.race([,])',
'NotPromise.race([promise])',
'Promise?.race([promise])',
'Promise.race?.([promise])',
'Promise.race(...[promise])',
'Promise.race([promise], extraArguments)',
'Promise.race()',
'new Promise.race([promise])',

// We are not checking these cases
'globalThis.Promise.all([promise])',
'Promise["all"]([promise])',
'globalThis.Promise.race([promise])',
'Promise["race"]([promise])',

// This can't be checked
'Promise.allSettled([promise])',
],
invalid: [
'Promise.all([promise,],)',
'Promise.race([promise,],)',
outdent`
foo
Promise.all([(0, promise),],)
Promise.race([(0, promise),],)
`,
outdent`
foo
Promise.all([[array][0],],)
Promise.race([[array][0],],)
`,
'Promise.all([promise]).then()',
'Promise.all([1]).then()',
'Promise.all([1.]).then()',
'Promise.all([.1]).then()',
'Promise.all([(0, promise)]).then()',
'const _ = () => Promise.all([ a ?? b ,],)',
'Promise.all([ {a} = 1 ,],)',
'Promise.all([ function () {} ,],)',
'Promise.all([ class {} ,],)',
'Promise.all([ new Foo ,],).then()',
'Promise.all([ new Foo ,],).toString',
'foo(Promise.all([promise]))',
'Promise.all([promise]).foo = 1',
'Promise.all([promise])[0] ||= 1',
'Promise.all([undefined]).then()',
'Promise.all([null]).then()',
'Promise.race([promise]).then()',
'Promise.race([1]).then()',
'Promise.race([1.]).then()',
'Promise.race([.1]).then()',
'Promise.race([(0, promise)]).then()',
'const _ = () => Promise.race([ a ?? b ,],)',
'Promise.race([ {a} = 1 ,],)',
'Promise.race([ function () {} ,],)',
'Promise.race([ class {} ,],)',
'Promise.race([ new Foo ,],).then()',
'Promise.race([ new Foo ,],).toString',
'foo(Promise.race([promise]))',
'Promise.race([promise]).foo = 1',
'Promise.race([promise])[0] ||= 1',
'Promise.race([undefined]).then()',
'Promise.race([null]).then()',
],
});

// `Promise.all`
test.snapshot({
valid: [],
invalid: [
// Only fixable if it's in `ExpressionStatement`
'Promise.all([promise])',
'await Promise.all([promise])',

'const foo = () => Promise.all([promise])',
'const foo = await Promise.all([promise])',
'foo = await Promise.all([promise])',

// `Promise.{all, race}()` should not care if the result is used
'const foo = await Promise.race([promise])',
'const foo = () => Promise.race([promise])',
'foo = await Promise.race([promise])',
'const results = await Promise.any([promise])',
'const results = await Promise.race([promise])',

// Fixable, but not provide at this point
'const [foo] = await Promise.all([promise])',
],
});
Loading
Loading