Issue #18438: Fix false positive in EmptyLineSeparator check#18545
Conversation
|
Github, generate report for EmptyLineSeparator/all-examples-in-one |
|
Report for EmptyLineSeparator/all-examples-in-one: |
| DetailAST reqAST = result; | ||
| if (reqAST.getNextSibling() != null) { | ||
| final Optional<DetailAST> postFixNode = getPostFixNode(reqAST.getNextSibling()); |
There was a problem hiding this comment.
reqAST is a temporary candidate node used during postfix handling.
It initially points to the current result, then allows the method to traverse into postfix-related AST structures (for example, method calls or increment expressions) without immediately overwriting result.
After the recursive traversal, reqAST is validated against the line constraint (reqAST.getLineNo() <= line). Only if it satisfies this condition is it promoted to result.
In short, reqAST acts as a safe checkpoint that holds a potential replacement for result while exploring postfix nodes, ensuring the final returned AST node still lies on or before the target line.
There was a problem hiding this comment.
can you explain it with AST?
Also the reqAST name doesn't make much sense, you need to rename it to something more meaningful.
There was a problem hiding this comment.
can you explain it with AST?
| |--SEMI -> ; [19:63]
| |--EXPR -> EXPR [23:13]
| | `--METHOD_CALL -> ( [23:13]
| | |--DOT -> . [23:9]
| | | |--SINGLE_LINE_COMMENT -> // [22:8]
| | | | `--COMMENT_CONTENT -> violation 2 lines below 'There is more than 1 empty line after this line.'\r\n [22:10]
| | | |--IDENT -> t [23:8]
| | | `--IDENT -> foo [23:10]
| | |--ELIST -> ELIST [23:14]
| | | `--EXPR -> EXPR [23:14]
| | | `--NUM_INT -> 10 [23:14]
| | `--RPAREN -> ) [23:16]
| |--SEMI -> ; [23:17]
Looking at the AST, there are two empty lines between the first SEMI token (; [19:63]) and the next EXPR token (EXPR [23:13]). Therefore, according to the method’s intent, the SEMI at line 19 should be identified as the last AST element before the empty lines.
if (result.getNextSibling() != null) {
final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling());
if (postFixNode.isPresent()) {
// A post fix AST will always have a sibling METHOD CALL
// METHOD CALL will at least have two children
// The first child is DOT in case of POSTFIX which have at least 2 children
// First child of DOT again puts us back to normal AST tree which will
// recurse down below from here
final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
}
}
The postfix-handling logic inspects result.getNextSibling() and detects a method call expression. Because of this, it recursively descends into the method call AST and updates result to the SEMI at line 23 (; [23:17]). This overrides the correct result, even though that node appears after the empty lines, which leads to a false positive.
To avoid this, reqAST is introduced as a temporary candidate that initially refers to the current result. If a possible postfix or method call expression is found, traversal continues and reqAST is updated accordingly. Before promoting it to the final result, its line number is validated against the target line:
if (reqAST.getLineNo() <= line) {
result = reqAST;
}
This ensures that postfix traversal does not incorrectly cross empty-line boundaries and that the returned AST node truly lies on or before the required line.
Also the reqAST name doesn't make much sense, you need to rename it to something more meaningful.
Is candidateAST an appropriate name? If not, could you please suggest a more meaningful one?
There was a problem hiding this comment.
can code
DetailAST reqAST = result;
if (reqAST.getNextSibling() != null) {
final Optional<DetailAST> postFixNode = getPostFixNode(reqAST.getNextSibling());
if (postFixNode.isPresent()) {
// A post fix AST will always have a sibling METHOD CALL
// METHOD CALL will at least have two children
// The first child is DOT in case of POSTFIX which have at least 2 children
// First child of DOT again puts us back to normal AST tree which will
// recurse down below from here
final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
reqAST = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
}
}
if (reqAST.getLineNo() <= line) {
result = reqAST;
}
be written like:
result = positionToPotentialPostFixNode(result);
in this case comment can be moved to javadoc and logic will be a bit more structred.
There was a problem hiding this comment.
@romani Thanks for the help, it is now more structred.
|
@Zopsss please review, i explained as you asked. |
|
@Praveen7294 , please se my comment above. please consider #18438 (comment) also. |
|
why we have this violation ? is this different defect in same Check? |
de133b2 to
96b8b50
Compare
Done.
This false positive occurs when we add below property in This property is not added in |
@romani The red one is for the base, and the green one is for the patch. Both are the same defect, but in the base code the violation references the wrong line, while in the patch code the violation references the correct line. |
|
Github, generate report for EmptyLineSeparator/all-examples-in-one |
|
Report for EmptyLineSeparator/all-examples-in-one: |
|
Ok, I checking violations all good. https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/96b8b50bbe62f142d0d4c1a228476862a61ea839_2026021830/reports/diff/spoon/index.html#A3 this looks like existing other problem. |
|
I reviewed all from bottom until Orekit, please go though top to Orekit to spot unusual case, and let me know when you finished |
I reviewed from top to openjdk25, there is no unusual violation. |
i reviewed and found there is no handling case of anonymous class with method chaining. Below example:
This is a case of chained method call and the issue is about method call, so it's better to solve in this PR. Sorry for the delay. I was working on this problem to identify the main cause. |
|
Please create issue, we can improve in separate issue/PR. |
15d9449 to
1504705
Compare
Done. |
|
But this #18545 (comment) is just comment that Check consider as empty line Not clear why it is new violation |
Yes, you are right. The check is comment-aware, but comments tokens are skipped(not consider as empty line) during AST traversal in the visitToken() method. This one is to skip comment before current token This one is to skip comment after current token but in one case comment tokens are not skipped when Without comment: cli: With comment: cli: you can see after adding comment difference in second violation. This clearly shows comment is not skipped inside class member block. This explanation is only for clearing confusion around comment.
This violation is due to anonymous class with chained method call. if you want i can explain with ast why this violation related to anonymous class with chained method call (not related to comment). |
|
EmptyLineSeparatorCheck#example7 There is more than 1 empty line after this line. 146 I am human, and I don't understand why checkstyle thinks that after 146 lines there is more than 1 empty lines. It is not true for sure |
see line 160. After this line, there are more than one empty lines. Due to this, the check tries to find the last token before the empty line. However, because of the chained method call, it is unable to determine the correct last token before the empty line. |
a061750 to
15d2caf
Compare
bdc820f to
9e6fad1
Compare
|
Github, generate report for EmptyLineSeparator/all-examples-in-one |
|
Report for EmptyLineSeparator/all-examples-in-one: |
3f09e03 to
2ec001b
Compare
|
@romani ping |
|
Github, generate report for EmptyLineSeparator/all-examples-in-one |
|
Report for EmptyLineSeparator/all-examples-in-one: |
romani
left a comment
There was a problem hiding this comment.
thanks a lot for this update.
All good.
|
Thanks a lot , sorry that we lost this PR is tracking |
Fix #18438