Issue #17223: New Check TextBlockGoogleStyleFormattingCheck#17329
Conversation
28c8773 to
2060baa
Compare
|
I think Indentation checking should be easy too. We first check if both quotes are at the start of line or not. And then check if both quotes are vertically aligned with tabs expanded. Get the starting column number of the line, for both - opening quotes & closing quotes. Check their starting column number value and see if it is same or not. If it is not same then log the violation. |
2060baa to
bc499b0
Compare
|
@Zopsss there can be a case where quotes are vertically aligned but at wrong Indentation value. |
7ca514c to
4099ebb
Compare
This should be part of the indentation check itself. In this check we should only focus on whether the quotes are vertically aligned or not. The JavadocLeadingAsteriskAlign does something similar:
It ensures that the leading asterisks in Javadoc comments are vertically aligned under the first asterisk ( This check does not enforce correct indentation. For example, if the entire Javadoc block is misaligned (e.g., starts at column 5 instead of 4), but the asterisks are properly aligned vertically, the check will still pass. Similarly, this new check should only be concerned with the vertical alignment of the quote characters. Whether the quotes are placed at the correct indentation level is outside its scope and should be handled by the indentation check. |
4099ebb to
5f76ebf
Compare
| /** | ||
| * Determines if the given expression is at the start of a line. | ||
| * | ||
| * @param ast the ast to check | ||
| * @return true if it is, false otherwise | ||
| */ | ||
| private boolean isStartOfLine(DetailAST ast) { | ||
| final int tabWidth = getTabWidth(); | ||
| final String line = getLine(ast.getLineNo() - 1); | ||
|
|
||
| int index = 0; | ||
| while (Character.isWhitespace(line.charAt(index))) { | ||
| index++; | ||
| } | ||
| return CommonUtil.lengthExpandedTabs(line, index, tabWidth) | ||
| == CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(), tabWidth); | ||
| } |
There was a problem hiding this comment.
Can we just get the text of the token and then verify if it is the first thing in the line or not. Regex can help us to verify if the quotes are the first thing present in the line or not. It'll be much simpler and more optimized.
There was a problem hiding this comment.
@Zopsss @romani
yes we can try using Regex, but don't we always push for more AST based approach in general?
what do you mean by "simpler"? if you mean less lines of code, then we can make this code much simpler. this is only first draft and I can already notice there is lot of duplicate code that can be reduced. We will have much simpler version of this code by the time this PR is ready for merging.
There was a problem hiding this comment.
We prefer AST based solutions.
But regexp some time are ok, as you might noticed with Todo. When regexp is simple, it might be ok
Your call to fall to AST based.
There was a problem hiding this comment.
I think Regex approach will be more suitable, it will reduce calls to other methods so it'll be a little more optimized.
There was a problem hiding this comment.
method to get content of line is deprecated, we try all to not use it, as you allow to use it for one reason, second reason will be even more easier to justify. But it is proven to be hell to maintain, but yes AST is more complicated in implementaton but more robust.. We should operate by AST.
There was a problem hiding this comment.
is this conversation resolved? how to proceed further here?
5f76ebf to
0d8e0dc
Compare
| !TokenUtil.areOnSameLine(ast, superParent); | ||
| }; | ||
| } | ||
|
|
There was a problem hiding this comment.
Ast based approach. more complex
Zopsss
left a comment
There was a problem hiding this comment.
We're on the right track!
pass 1:
| final DetailAST closingQuotes = getClosingQuotes(ast); | ||
|
|
||
| if (!areOpeningQuotesOnCorrectPosition(ast)) { | ||
| log(ast, MSG_OPEN_QUOTES_ERROR); | ||
| } | ||
|
|
||
| if (!areClosingQuotesOnCorrectPosition(getClosingQuotes(ast))) { | ||
| log(closingQuotes, MSG_CLOSE_QUOTES_ERROR); | ||
| } |
There was a problem hiding this comment.
| final DetailAST closingQuotes = getClosingQuotes(ast); | |
| if (!areOpeningQuotesOnCorrectPosition(ast)) { | |
| log(ast, MSG_OPEN_QUOTES_ERROR); | |
| } | |
| if (!areClosingQuotesOnCorrectPosition(getClosingQuotes(ast))) { | |
| log(closingQuotes, MSG_CLOSE_QUOTES_ERROR); | |
| } | |
| if (!areOpeningQuotesOnCorrectPosition(ast)) { | |
| log(ast, MSG_OPEN_QUOTES_ERROR); | |
| } | |
| final DetailAST closingQuotes = getClosingQuotes(ast); | |
| if (!areClosingQuotesOnCorrectPosition(closingQuotes)) { | |
| log(closingQuotes, MSG_CLOSE_QUOTES_ERROR); | |
| } |
| * @return true if the opening quotes are on the new line. | ||
| */ | ||
| private static boolean areOpeningQuotesOnCorrectPosition(DetailAST openingQuotes) { | ||
| DetailAST superParent = openingQuotes; |
There was a problem hiding this comment.
DetailAST parent = openingQuotes;
| while (!TokenUtil.isOfType(superParent.getType(), TokenTypes.LITERAL_RETURN, | ||
| TokenTypes.ASSIGN, TokenTypes.LITERAL_RETURN, TokenTypes.METHOD_CALL, | ||
| TokenTypes.PLUS)) { | ||
|
|
||
| superParent = superParent.getParent(); | ||
|
|
||
| if (superParent.getType() == TokenTypes.PLUS | ||
| && plusIsBetweenTwoTextBlocks(openingQuotes, superParent)) { | ||
| superParent = superParent.getParent(); | ||
| } | ||
| } |
There was a problem hiding this comment.
while iterating over the parents, can we check if the opening quotes and parent are on same line? if they're not then we can break the loop as the opening quotes will be on new line so no need to check anything further.
There was a problem hiding this comment.
Done.
I redid the entire logic based on your suggestion. It's more cleaner and easier to understand now. I really feel new logic is more robust
| if (superParent.getType() == TokenTypes.METHOD_CALL) { | ||
| result = checkMethodCall(openingQuotes, superParent); | ||
| } | ||
| else { | ||
| result = !TokenUtil.areOnSameLine(openingQuotes, superParent); | ||
| } | ||
| return result; |
There was a problem hiding this comment.
| if (superParent.getType() == TokenTypes.METHOD_CALL) { | |
| result = checkMethodCall(openingQuotes, superParent); | |
| } | |
| else { | |
| result = !TokenUtil.areOnSameLine(openingQuotes, superParent); | |
| } | |
| return result; | |
| result = !TokenUtil.areOnSameLine(openingQuotes, superParent); | |
| if (superParent.getType() == TokenTypes.METHOD_CALL && !result) { | |
| result = checkMethodCall(openingQuotes, superParent); | |
| } | |
| return result; |
first check if opening quotes and parent are on the same line or not. If not then return true. If the parent is type of Method Call and parent & opening quotes are not on same line then invoke the further logic.
| * @param methodCall the methodCall | ||
| * @return true | ||
| */ | ||
| private static boolean checkMethodCall(DetailAST ast, DetailAST methodCall) { |
There was a problem hiding this comment.
(DetailAST openingQuotes, DetailAST methodCall)
| * @return true | ||
| */ | ||
| private static boolean checkMethodCall(DetailAST ast, DetailAST methodCall) { | ||
| DetailAST tmp = methodCall.getFirstChild(); |
There was a problem hiding this comment.
what is tmp? Please use better name
| } | ||
|
|
||
| else { | ||
| DetailAST expList = methodCall.findFirstToken(TokenTypes.ELIST); |
| boolean result = !TokenUtil.areOnSameLine(ast, tmp); | ||
|
|
||
| if (tmp.getType() == TokenTypes.DOT && tmp.getFirstChild() != ast) { | ||
| result = !TokenUtil.areOnSameLine(ast, tmp); | ||
| } |
There was a problem hiding this comment.
we're repeating the same logic here. I don't think this if statement is needed.
| while (text.charAt(index) == ' ') { | ||
| index--; | ||
| } | ||
| char test = text.charAt(index); | ||
| return text.charAt(index) == '\n'; |
There was a problem hiding this comment.
instead of using while loop, can we directly check if the 0th index is \n or not?
There was a problem hiding this comment.
Im not sure I follow, 0th index?
We are looking for \n from the last index. after eliminating whitespaces, if we reach a '\n' then we know Closing quotes are on the new line. but if we reach any other character, then we place violation for closing quotes not being on new line.
--TEXT_BLOCK_LITERAL_BEGIN -> """ [108:18]
|--TEXT_BLOCK_CONTENT -> \n some String\n [108:21]
`--TEXT_BLOCK_LITERAL_END -> """ [110:12]
There was a problem hiding this comment.
ah sorry my bad, I misunderstood the code.
| // 'Text-block quotes are not vertically aligned.' | ||
| getData(""" | ||
| first string | ||
| """ + """ |
There was a problem hiding this comment.
is opening quotes allowed to be preceded by +? How does formatter tackle this code?
There was a problem hiding this comment.
No. violation comment for that is on 114 line.
Formatter fails here. It made the opening quotes preceded by +.
--- TextBlocksOnLine.java 2025-08-27 12:03:51.928243400 +0530
+++ InputFormattedTextBlockGoogleStyleTree.java 2025-08-27 12:03:55.051129600 +0530
@@ -1,15 +1,16 @@
public class TextBlocksOnLine {
void method1() {
- getData("""
+ getData(
+ """
first string
- """ + """
- some String
- """,
"""
- second string
+ + """
+ some String
+ """,
"""
- );
- }
+ second string
+ """);
+ }
There was a problem hiding this comment.
nice catch. Report it to their github repository.
c8f7ea3 to
7007ab5
Compare
| * </li> | ||
| * </ul> | ||
| * | ||
| * @since 11.0.0 |
| } | ||
|
|
||
| @Test | ||
| public void testCommitMessageHasProperStructure() throws Exception { |
There was a problem hiding this comment.
nevermind those changes. we are still in testing phrase and this test was bothering me a lot in the build process
| final String message = """ | ||
| final String message = | ||
| """ |
There was a problem hiding this comment.
why this was changed? Similar changes are made to other files too.
There was a problem hiding this comment.
I started getting build errors from this Check
There was a problem hiding this comment.
@romani I haven't even added this check to checkstyle-checks.xml, why have we started getting errors from it already?
|
@mohitsatr please start generating regression reports. |
|
@mohitsatr ping |
|
Github, generate report |
|
checker gave an exception at: https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/7007ab5_2025074153/reports/diff/apache-struts/index.html#A17 @mohitsatr can you investigate it? Exception was thrown at following line: But it was for for a multi-line comment, so it maybe not due to this check. Maybe something else caused it: |
|
False-negative. Opening quotes are not violated: Case: private static String createMigrationRejectionDueConcurrentModificationReason(
final long processInstanceKey) {
return """
Expected to migrate process instance '%d' \
but a concurrent command was executed on the process instance. \
Please retry the migration."""
.formatted(processInstanceKey);
}Edit: similar case: https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/7007ab5_2025074153/reports/diff/camunda/index.html#A423 |
|
Found similar case again, in Checkstyle codebase: 57th line is not marked as violation. Another one: https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/7007ab5_2025074153/reports/diff/checkstyle/index.html#A101 41st line is not violated. |
7007ab5 to
093a01b
Compare
|
Github, generate report |
d34fb63 to
d90a7a6
Compare
Zopsss
left a comment
There was a problem hiding this comment.
Thank you @mohitsatr for all the hard work! This check will help us to cover a huge gap in our style guide implementation! :D
d90a7a6 to
52a16bb
Compare
52a16bb to
d74722a
Compare
|
Ok , final challenge is to pass CI Should be simple to fix from semaphore: Circleci: |
d74722a to
904f111
Compare
So it conflicts with release dry run |
09a2e86 to
309b608
Compare
@mohitsatr , I pushed fixes for all previous problems. Before you proceed, pull branch from remote yo your local |
thanks! @romani By the way, how did you find out the main issue? Did you run the release_dry locally? |
|
Github, generate report |
Yes, all commands of CI are executable on Linux local. This PR is hanging for so long I decided to use my rare laptop time to move this PR across finish line, but :) faced more blockers. |
309b608 to
20d9764
Compare
|
Github, generate report |
20d9764 to
5b69f8e
Compare
romani
left a comment
There was a problem hiding this comment.
It was huge effort. Great JOB !!!
Even I impressed how complicated it is to make new Check in Checkstyle. But it works and hope there will be no problems for users.
Thanks a lot to all participants
fixes #17223
New module config: https://gist.githubusercontent.com/mohitsatr/0e04070f847926ea44bf7eda88706807/raw/f9470db307d730a651e9c93cad776d6b592839e8/textblockgooglestyleformatting.xml
Contribution repo: checkstyle/contribution#955