Skip to content

Issue #17223: New Check TextBlockGoogleStyleFormattingCheck#17329

Merged
romani merged 1 commit into
checkstyle:masterfrom
mohitsatr:textblockgooglestyle
Dec 11, 2025
Merged

Issue #17223: New Check TextBlockGoogleStyleFormattingCheck#17329
romani merged 1 commit into
checkstyle:masterfrom
mohitsatr:textblockgooglestyle

Conversation

@mohitsatr

@mohitsatr mohitsatr commented Jul 8, 2025

Copy link
Copy Markdown
Member

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch 2 times, most recently from 28c8773 to 2060baa Compare July 12, 2025 11:15

@Zopsss Zopsss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Item:

@Zopsss

Zopsss commented Jul 13, 2025

Copy link
Copy Markdown
Member

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.

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 2060baa to bc499b0 Compare July 14, 2025 10:22
@mohitsatr

Copy link
Copy Markdown
Member Author

@Zopsss there can be a case where quotes are vertically aligned but at wrong Indentation value.
For checking indentation, we will first find the indentation of a opening quotes and indentation of it's parent element (anything that's one line above it). if the difference between the two values is 0 or 4 (lineWrappingIndentation), then it's is at correct position.

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch 2 times, most recently from 7ca514c to 4099ebb Compare July 15, 2025 04:11
@Zopsss

Zopsss commented Jul 15, 2025

Copy link
Copy Markdown
Member

we will first find the indentation of a opening quotes and indentation of it's parent element (anything that's one line above it). if the difference between the two values is 0 or 4 (lineWrappingIndentation), then it's is at correct position.

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:

The Check ensures that leading asterisks are aligned vertically under the first asterisk ( * ) of opening Javadoc tag. The alignment of closing Javadoc tag ( */ ) is also checked.

It ensures that the leading asterisks in Javadoc comments are vertically aligned under the first asterisk (*) of the opening Javadoc tag. It also verifies the alignment of the closing tag (*/).

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.

Comment on lines +149 to +165
/**
* 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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@mohitsatr mohitsatr Aug 12, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think Regex approach will be more suitable, it will reduce calls to other methods so it'll be a little more optimized.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

is this conversation resolved? how to proceed further here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should go with the AST base approach. @mohitsatr

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 5f76ebf to 0d8e0dc Compare August 18, 2025 08:29
!TokenUtil.areOnSameLine(ast, superParent);
};
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ast based approach. more complex

@Zopsss Zopsss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We're on the right track!

pass 1:

Comment on lines +109 to +117
final DetailAST closingQuotes = getClosingQuotes(ast);

if (!areOpeningQuotesOnCorrectPosition(ast)) {
log(ast, MSG_OPEN_QUOTES_ERROR);
}

if (!areClosingQuotesOnCorrectPosition(getClosingQuotes(ast))) {
log(closingQuotes, MSG_CLOSE_QUOTES_ERROR);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
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);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

* @return true if the opening quotes are on the new line.
*/
private static boolean areOpeningQuotesOnCorrectPosition(DetailAST openingQuotes) {
DetailAST superParent = openingQuotes;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

DetailAST parent = openingQuotes;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines +171 to +181
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();
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Comment on lines +184 to +190
if (superParent.getType() == TokenTypes.METHOD_CALL) {
result = checkMethodCall(openingQuotes, superParent);
}
else {
result = !TokenUtil.areOnSameLine(openingQuotes, superParent);
}
return result;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

* @param methodCall the methodCall
* @return true
*/
private static boolean checkMethodCall(DetailAST ast, DetailAST methodCall) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(DetailAST openingQuotes, DetailAST methodCall)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

* @return true
*/
private static boolean checkMethodCall(DetailAST ast, DetailAST methodCall) {
DetailAST tmp = methodCall.getFirstChild();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what is tmp? Please use better name

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

}

else {
DetailAST expList = methodCall.findFirstToken(TokenTypes.ELIST);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

expList -> expressionList

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines +213 to +217
boolean result = !TokenUtil.areOnSameLine(ast, tmp);

if (tmp.getType() == TokenTypes.DOT && tmp.getFirstChild() != ast) {
result = !TokenUtil.areOnSameLine(ast, tmp);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we're repeating the same logic here. I don't think this if statement is needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines +248 to +252
while (text.charAt(index) == ' ') {
index--;
}
char test = text.charAt(index);
return text.charAt(index) == '\n';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

instead of using while loop, can we directly check if the 0th index is \n or not?

@mohitsatr mohitsatr Aug 25, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ah sorry my bad, I misunderstood the code.

// 'Text-block quotes are not vertically aligned.'
getData("""
first string
""" + """

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is opening quotes allowed to be preceded by +? How does formatter tackle this code?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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
+        """);
+  }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice catch. Report it to their github repository.

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from c8f7ea3 to 7007ab5 Compare August 27, 2025 13:47

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

items:

* </li>
* </ul>
*
* @since 11.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

11.1.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

}

@Test
public void testCommitMessageHasProperStructure() throws Exception {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

restore.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nevermind those changes. we are still in testing phrase and this test was bothering me a lot in the build process

Comment on lines -81 to +82
final String message = """
final String message =
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why this was changed? Similar changes are made to other files too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I started getting build errors from this Check

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@romani I haven't even added this check to checkstyle-checks.xml, why have we started getting errors from it already?

@Zopsss

Zopsss commented Aug 30, 2025

Copy link
Copy Markdown
Member

@mohitsatr please start generating regression reports.

@Zopsss

Zopsss commented Sep 2, 2025

Copy link
Copy Markdown
Member

@mohitsatr ping

@mohitsatr

Copy link
Copy Markdown
Member Author

Github, generate report

@github-actions

github-actions Bot commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

@Zopsss

Zopsss commented Sep 3, 2025

Copy link
Copy Markdown
Member

@Zopsss

Zopsss commented Sep 3, 2025

Copy link
Copy Markdown
Member

False-negative. Opening quotes are not violated:

https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/7007ab5_2025074153/reports/diff/camunda/index.html#A421

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

@Zopsss

Zopsss commented Sep 3, 2025

Copy link
Copy Markdown
Member

@Zopsss

Zopsss commented Sep 3, 2025

Copy link
Copy Markdown
Member

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 7007ab5 to 093a01b Compare September 4, 2025 07:35
@mohitsatr

Copy link
Copy Markdown
Member Author

Github, generate report

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from d34fb63 to d90a7a6 Compare November 28, 2025 02:05

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

last:

Comment thread src/site/xdoc/checks/coding/textblockgooglestyleformatting.xml.template Outdated

@Zopsss Zopsss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you @mohitsatr for all the hard work! This check will help us to cover a huge gap in our style guide implementation! :D

@romani

romani commented Dec 2, 2025

Copy link
Copy Markdown
Member

@romani romani force-pushed the textblockgooglestyle branch from d90a7a6 to 52a16bb Compare December 2, 2025 12:23

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

one more:

Comment thread src/site/xdoc/checks/coding/textblockgooglestyleformatting.xml.template Outdated
@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 52a16bb to d74722a Compare December 3, 2025 02:46
@romani

romani commented Dec 3, 2025

Copy link
Copy Markdown
Member

Ok , final challenge is to pass CI

Should be simple to fix from semaphore:
List of missing files in pitest profiles: src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/TextBlockGoogleStyleFormattingCheck.java

Circleci:
./.ci/validation.sh release-dry-run hmmmm not very clear

@romani romani force-pushed the textblockgooglestyle branch from d74722a to 904f111 Compare December 4, 2025 15:22
@romani

romani commented Dec 4, 2025

Copy link
Copy Markdown
Member

ERROR] XdocsPagesTest.testAllXmlExamples:701->getTagSourcesNode:481 src/site/xdoc/checks/coding/textblockgooglestyleformatting.xml has invalid xml (The element type "p" must be terminated by the matching end-tag "</p>".): <?xml version="1.0" encoding="UTF-8"?>

So it conflicts with release dry run

@romani romani force-pushed the textblockgooglestyle branch 3 times, most recently from 09a2e86 to 309b608 Compare December 5, 2025 00:46
@romani

romani commented Dec 5, 2025

Copy link
Copy Markdown
Member
+  <mutation unstable="false">
+    <sourceFile>TextBlockGoogleStyleFormattingCheck.java</sourceFile>
+    <mutatedClass>com.puppycrawl.tools.checkstyle.checks.coding.TextBlockGoogleStyleFormattingCheck</mutatedClass>
+    <mutatedMethod>closingQuotesAreAloneOnTheLine</mutatedMethod>
+    <mutator>org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator</mutator>
+    <description>removed call to java/lang/String::charAt</description>
+    <lineContent>while (text.charAt(index) == &apos; &apos;) {</lineContent>
+  </mutation>
+
+  <mutation unstable="false">
+    <sourceFile>TextBlockGoogleStyleFormattingCheck.java</sourceFile>
+    <mutatedClass>com.puppycrawl.tools.checkstyle.checks.coding.TextBlockGoogleStyleFormattingCheck</mutatedClass>
+    <mutatedMethod>closingQuotesAreAloneOnTheLine</mutatedMethod>
+    <mutator>org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_ELSE</mutator>
+    <description>removed conditional - replaced equality check with false</description>
+    <lineContent>while (text.charAt(index) == &apos; &apos;) {</lineContent>
+  </mutation>
+
+  <mutation unstable="false">
+    <sourceFile>TextBlockGoogleStyleFormattingCheck.java</sourceFile>
+    <mutatedClass>com.puppycrawl.tools.checkstyle.checks.coding.TextBlockGoogleStyleFormattingCheck</mutatedClass>
+    <mutatedMethod>quotesArePrecededWithComma</mutatedMethod>
+    <mutator>org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF</mutator>
+    <description>removed conditional - replaced equality check with true</description>
+    <lineContent>return expression.getType() == TokenTypes.EXPR</lineContent>
+  </mutation>

@mohitsatr , I pushed fixes for all previous problems.
Here are new , pitest was not executed before. Please fix mutations

Before you proceed, pull branch from remote yo your local

@mohitsatr

mohitsatr commented Dec 6, 2025

Copy link
Copy Markdown
Member Author

I pushed fixes for all previous problems.

thanks!

@romani By the way, how did you find out the main issue? Did you run the release_dry locally?

@mohitsatr

Copy link
Copy Markdown
Member Author

Github, generate report

@github-actions

Copy link
Copy Markdown
Contributor

@romani

romani commented Dec 10, 2025

Copy link
Copy Markdown
Member

Did you run the release_dry locally?

Yes, all commands of CI are executable on Linux local.
All CI prints a command they executed.

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.

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 309b608 to 20d9764 Compare December 11, 2025 02:35
@mohitsatr

Copy link
Copy Markdown
Member Author

Github, generate report

@github-actions

Copy link
Copy Markdown
Contributor

@mohitsatr mohitsatr force-pushed the textblockgooglestyle branch from 20d9764 to 5b69f8e Compare December 11, 2025 04:33

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

@romani romani merged commit 43c7ae4 into checkstyle:master Dec 11, 2025
123 checks passed
@mohitsatr mohitsatr deleted the textblockgooglestyle branch December 13, 2025 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Google-style: New Check TextBlockGoogleStyleFormattingCheck

3 participants