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

Fix: no-plusplus allow comma operands in for afterthought (fixes #13005) #13024

Merged
merged 2 commits into from Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 23 additions & 3 deletions docs/rules/no-plusplus.md
Expand Up @@ -71,10 +71,30 @@ Examples of **correct** code for this rule with the `{ "allowForLoopAfterthought
/*eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }]*/

for (i = 0; i < l; i++) {
return;
doSomething(i);
}

for (i = 0; i < l; i--) {
return;
for (i = l; i >= 0; i--) {
doSomething(i);
}

for (i = 0, j = l; i < l; i++, j--) {
doSomething(i, j);
}
```

Examples of **incorrect** code for this rule with the `{ "allowForLoopAfterthoughts": true }` option:

```js
/*eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }]*/

for (i = 0; i < l; j = i++) {
doSomething(i, j);
}

for (i = l; i--;) {
doSomething(i);
}

for (i = 0; i < l;) i++;
```
35 changes: 32 additions & 3 deletions lib/rules/no-plusplus.js
Expand Up @@ -6,6 +6,34 @@

"use strict";

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Determines whether the given node is the update node of a `ForStatement`.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is `ForStatement` update.
*/
function isForStatementUpdate(node) {
const parent = node.parent;

return parent.type === "ForStatement" && parent.update === node;
}

/**
* Determines whether the given node is considered to be a for loop "afterthought" by the logic of this rule.
* In particular, if it is the update node of a `ForStatement`, or an operand of a sequence expression that is the update node.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a for loop afterthought.
*/
function isForLoopAfterthought(node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @yeonjuan mentioned, it appears that this check only looks up one level, which could mean some patterns will still be flagged as incorrect.

for (;; (++a, (++b, ++c) ) {}  // it's warned in the PR version. no-plusplus for `b` and `c`

It may be an unlikely case, but for the sake of completeness, it probably makes sense to continue looking up the AST if the parent is a SequenceExpression.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done now! It makes sense to me, too.

const parent = node.parent;

return isForStatementUpdate(node) ||
parent.type === "SequenceExpression" && isForStatementUpdate(parent);
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -42,18 +70,19 @@ module.exports = {
create(context) {

const config = context.options[0];
let allowInForAfterthought = false;
let allowForLoopAfterthoughts = false;

if (typeof config === "object") {
allowInForAfterthought = config.allowForLoopAfterthoughts === true;
allowForLoopAfterthoughts = config.allowForLoopAfterthoughts === true;
}

return {

UpdateExpression(node) {
if (allowInForAfterthought && node.parent.type === "ForStatement") {
if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) {
return;
}

context.report({
node,
messageId: "unexpectedUnaryOp",
Expand Down
61 changes: 60 additions & 1 deletion tests/lib/rules/no-plusplus.js
Expand Up @@ -24,7 +24,11 @@ ruleTester.run("no-plusplus", rule, {

// With "allowForLoopAfterthoughts" allowed
{ code: "var foo = 0; foo=+1;", options: [{ allowForLoopAfterthoughts: true }] },
{ code: "for (i = 0; i < l; i++) { console.log(i); }", options: [{ allowForLoopAfterthoughts: true }] }
{ code: "for (i = 0; i < l; i++) { console.log(i); }", options: [{ allowForLoopAfterthoughts: true }] },
{ code: "for (var i = 0, j = i + 1; j < example.length; i++, j++) {}", options: [{ allowForLoopAfterthoughts: true }] },
{ code: "for (;; i--, foo());", options: [{ allowForLoopAfterthoughts: true }] },
{ code: "for (;; foo(), --i);", options: [{ allowForLoopAfterthoughts: true }] },
{ code: "for (;; foo(), ++i, bar);", options: [{ allowForLoopAfterthoughts: true }] }
],

invalid: [
Expand Down Expand Up @@ -81,6 +85,61 @@ ruleTester.run("no-plusplus", rule, {
},
type: "UpdateExpression"
}]
},
{
code: "for (i++;;);",
options: [{ allowForLoopAfterthoughts: true }],
errors: [{
messageId: "unexpectedUnaryOp",
data: {
operator: "++"
},
type: "UpdateExpression"
}]
},
{
code: "for (;--i;);",
options: [{ allowForLoopAfterthoughts: true }],
errors: [{
messageId: "unexpectedUnaryOp",
data: {
operator: "--"
},
type: "UpdateExpression"
}]
},
{
code: "for (;;) ++i;",
options: [{ allowForLoopAfterthoughts: true }],
errors: [{
messageId: "unexpectedUnaryOp",
data: {
operator: "++"
},
type: "UpdateExpression"
}]
},
{
code: "for (;; i = j++);",
options: [{ allowForLoopAfterthoughts: true }],
errors: [{
messageId: "unexpectedUnaryOp",
data: {
operator: "++"
},
type: "UpdateExpression"
}]
},
{
code: "for (;; i++, f(--j));",
Copy link
Member

@yeonjuan yeonjuan Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking.Just questions for understanding the proper behavior of this option.

It seems the option{allowForLoopAfterthoughts: true} changed to allow update expressions in sequence expression which has exactly one depth from for-loop update.

so the case like f(--j) is still warned.

And so, the below cases(nested sequence expression) are intended in the same manner?

for (;; (++a, (++b, ++c) ) {}  // it's warned in the PR version. no-plusplus for `b` and `c`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't think of that. It's done now, thanks!

options: [{ allowForLoopAfterthoughts: true }],
errors: [{
messageId: "unexpectedUnaryOp",
data: {
operator: "--"
},
type: "UpdateExpression"
}]
}
]
});