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 #2305: do not apply UnnecessaryBlock for the ECMAScript (JavaScript) destructuring assignments #4829

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,17 @@ be misleading. Considering removing this unnecessary Block.
<property name="xpath">
<value>
<![CDATA[
//Block[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop
or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)]
|
//Scope[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop
or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)]
//(Block|Scope)
[not(
parent::FunctionNode or
parent::IfStatement or
parent::ForLoop or
parent::ForInLoop or
parent::WhileLoop or
parent::DoLoop or
parent::TryStatement or
parent::CatchClause
)]
]]>
</value>
</property>
Expand Down
Copy link
Member

Choose a reason for hiding this comment

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

Please revert the changes in this file completely

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

import net.sourceforge.pmd.testframework.PmdRuleTst;

class UnnecessaryBlockTest extends PmdRuleTst {
public class UnnecessaryBlockTest extends PmdRuleTst {
// no additional unit tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ for (var i in obj) {
]]></code>
</test-code>

<test-code>
<description>Ok, for of</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
for (let value of obj) {
}
]]></code>
</test-code>

<test-code>
<description>Ok, while</description>
<expected-problems>0</expected-problems>
Expand Down Expand Up @@ -192,4 +201,44 @@ try {
}
]]></code>
</test-code>

<test-code>
<description>Ok, destructure assigments</description>
Copy link
Member

Choose a reason for hiding this comment

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

Note, that this sample code doesn't produce a UnnecessaryBlock, even with your rule improvements.

The only way to reproduce the bug is by not using semicolons. A simple example is

import { foo } from './someScript.js' // missing semicolon
const { isLoggedIn } = useAuth();  // <--- violation here: unnecessary block

(I've updated #2305 meanwhile)

Please add a separate test case for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[not(preceding::EmptyStatement)] fixes not using semicolons case, but it not violates in these cases "Bad, for", "Bad, for in", "Bad, if", "Bad, try".

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   "Bad, for" failed
[ERROR] ruleTests().Bad, for in
[INFO]   Run 1: PASS
[ERROR]   Run 2: "Bad, for in" failed
[INFO] 
[ERROR]   "Bad, if" failed
[ERROR]   "Bad, try" failed
[INFO] 
[ERROR] Tests run: 154, Failures: 0, Errors: 4, Skipped: 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed by this rule:

                    //Scope[
                        count(ancestor::*) = 1
                        and not(preceding::EmptyStatement)
                    ]
                    | //SwitchCase[./Scope]
                    | //(Scope|Block)[
                        ./(Scope|Block)
                        and count(./*) = 1
                    ]

First (//Scope) for root Blocks
Second (//SwitchCase) for Switch Statements
And Third — for everything else.

<expected-problems>0</expected-problems>
<code><![CDATA[
import {foo} from 'bar'

const [a, b] = array;
const [a, , b] = array;
const [a = aDefault, b] = array;
const [a, b, ...rest] = array;
const [a, , b, ...rest] = array;
const [a, b, ...{ pop, push }] = array;
const [a, b, ...[c, d]] = array;
Copy link
Member

Choose a reason for hiding this comment

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

If you remove the semicolon at the end of this line, then the rule will find a unnecessary block for the following { a, b }, because the Rhino parser doesn't understand this line...

It seems, that Rhino produces an "EmptyStatement", if it doesn't understand the syntax and tries to continue parsing with the next statement.

We can make use of this, and avoid some more false positives, if we check, whether the preceding node in the AST is a EmptyStatement, e.g.

//(Block|Scope)[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop or parent::ForOfLoop
    or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause
    or parent::VariableDeclarator or parent::ImportDeclaration)][not(preceding::EmptyStatement)]

Maybe we should duplicate this test case, but remove all semicolons.

Note, that the original sample code on #2305 (which is only the import statement) doesn't reproduce the problem... since import is parsed into a EmptyStatement and there is neither a Block nor a Scope...


const { a, b } = obj;
const { a: a1, b: b1 } = obj;
const { a: a1 = aDefault, b = bDefault } = obj;
const { a, b, ...rest } = obj;
const { a: a1, b: b1, ...rest } = obj;
const { [key]: a } = obj;

let a, b, a1, b1, c, d, rest, pop, push;
[a, b] = array;
[a, , b] = array;
[a = aDefault, b] = array;
[a, b, ...rest] = array;
[a, , b, ...rest] = array;
[a, b, ...{ pop, push }] = array;
[a, b, ...[c, d]] = array;

({ a, b } = obj); // parentheses are required
({ a: a1, b: b1 } = obj);
({ a: a1 = aDefault, b = bDefault } = obj);
({ a, b, ...rest } = obj);
({ a: a1, b: b1, ...rest } = obj);

function fn( ({arg}) ){ return arg}
]]></code>
</test-code>
</test-data>