Skip to content

Spotless / Checkstyle & Code Reformatting #14903

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

Open
wants to merge 7 commits into
base: 7.0.x
Choose a base branch
from
Open

Conversation

jdaugherty
Copy link
Contributor

@jdaugherty jdaugherty commented Jul 14, 2025

Please see below comments on the impact of this PR. This PR will enable nohttp, code linting, and code validation for java files. Separately, we will apply groovy validations via code narc. Moreover, this PR does reformat the entire code base (non-test code) for the first time to have a consistent style.

@jdaugherty
Copy link
Contributor Author

jdaugherty commented Jul 14, 2025

The style being applied is the style in grails-forge. It has this useful information on why this style was chosen:

Checkstyle configuration that checks the sun coding conventions from:

- the Java Language Specification at
  http://java.sun.com/docs/books/jls/second_edition/html/index.html

- the Sun Code Conventions at http://java.sun.com/docs/codeconv/

- the Javadoc guidelines at
  http://java.sun.com/j2se/javadoc/writingdoccomments/index.html

- the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html

- some best practices

A couple pain points with this:

  • I'm only reformatting java & groovy production files. This change will only validate the java files - codenarc will do groovy and have to come after. Tests are being done in a later PR.
  • redundant declarations (final) are forced to be removed
  • ordering of the variable type matters (static comes before instance, public comes before private, etc)
  • inner class declarations must occur at the end of the file
  • inline assignments are discouraged
  • empty if statements are discouraged (at least add a comment)
  • static, final variables need to follow the upper case convention
  • accessors are supposed to exist for any public field on a java class (although , I've turned off check style for anything that would be considered a breaking change)
  • the JLS specifies the order of modifiers - public, abstract, static, final, etc - and they seem to be wrong in the grails code base
  • the older array syntax needs updated - ie char myVar[] vs char[] myVar ... did anyone else forget Java originally supported this?
  • javadoc has to be formatted correctly - when having code in javadoc, you still have to have the asterisk on each line or the reformat will collapse it to a single line.
  • I'm converting wildcard to single imports per previous discussion
  • duplicate lines seem to exist throughout the project (historical merges, etc). These have to be removed.
  • If a comment that is unrelated to licensing existed and doesn't exist now, I've removed it rather than have a broken link in the code as part of the https validation.

@jdaugherty jdaugherty force-pushed the style-enforcement branch 4 times, most recently from 9132193 to afd7270 Compare July 15, 2025 01:16
@jdaugherty jdaugherty force-pushed the style-enforcement branch 4 times, most recently from 35eb937 to bab5cb6 Compare July 15, 2025 02:13
@jdaugherty jdaugherty marked this pull request as ready for review July 15, 2025 02:14
@jdaugherty
Copy link
Contributor Author

#13754

Copy link

@borinquenkid borinquenkid left a comment

Choose a reason for hiding this comment

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

Instead of failing the build for checkstyle, I would suggest putting a githook on prepush to apply spotless
` #!/bin/sh

echo "[git hook] Running Spotless apply before push..."

# Run spotlessApply
./gradlew spotlessApply

# Check if there are any changes after spotlessApply
if ! git diff --quiet; then
    echo "Spotless applied changes. Creating a separate commit..."
    git add .
    git commit -m "chore: Apply Spotless formatting" --no-verify # --no-verify to prevent recursion
fi

# Proceed with the original push
exit 0`

I have only used it on maven, so I can't suggest a gradle plugin

@jamesfredley jamesfredley moved this to In Progress in Apache Grails 7.0.x Jul 15, 2025
@jamesfredley jamesfredley added this to the grails:7.0.0-RC1 milestone Jul 15, 2025
@jamesfredley jamesfredley linked an issue Jul 15, 2025 that may be closed by this pull request
@jdaugherty
Copy link
Contributor Author

Instead of failing the build for checkstyle, I would suggest putting a githook on prepush to apply spotless ` #!/bin/sh

echo "[git hook] Running Spotless apply before push..."

# Run spotlessApply
./gradlew spotlessApply

# Check if there are any changes after spotlessApply
if ! git diff --quiet; then
    echo "Spotless applied changes. Creating a separate commit..."
    git add .
    git commit -m "chore: Apply Spotless formatting" --no-verify # --no-verify to prevent recursion
fi

# Proceed with the original push
exit 0`

I have only used it on maven, so I can't suggest a gradle plugin

I think it's reasonable to not fail the build on styling, but I do think we need a Github workflow that does fail if the styling is wrong. A prehook would be one way to help avoid issues, but it won't avoid all of them - some of the checkstyle configuration isn't fixable by spotless. For example, checkstyle mandates variable declarations be in certain order by their access level & classes be defined at the end of a file.

What are your thoughts about this:

  1. suggest the prehook for formatting purposes
  2. add a build that exclusively runs the checkstyle / linting (if linting shows anything changed, we would fail too)?

You can then iterate locally and fix after the fact.

@jamesfredley
Copy link
Contributor

https://desktop.github.com/download/ is a good option for reviewing this PR. There are too many changes for the browser and it has same unified and split view options.

@codeconsole
Copy link
Contributor

codeconsole commented Jul 16, 2025

Nice work.

Hard to view the change set because there are so many files and it crashes my browser.

What is the reason for annotation parameters having a space? That seems a little weird, no?

@DelegateAsync (BookApi)

@jdaugherty
Copy link
Contributor Author

Nice work.

Hard to view the change set because there are so many files and it crashes my browser.

What is the reason for annotation parameters having a space? That seems a little weird, no?

@DelegateAsync (BookApi)

That is odd. All I did here was format / spotless apply / checkstyle and then fix the remaining errors for checkstyle rules that forge had defined. If someone can suggest changes to the configuration, we can easily reformat the 2 commits that were styling related.

@jamesfredley
Copy link
Contributor

DelegateAsync (BookApi) with the space appears to only occur in Groovydoc

* @DelegateAsync (BookApi)

Copy link
Contributor

@jamesfredley jamesfredley left a comment

Choose a reason for hiding this comment

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

In Converter.java I don't think we want to remove the public modifiers, since it is a java file. This is the only java file, I found, where this occurred.

We have a mix of

try {
}
catch (..) {
}

and

try {
} catch (..) {
}

with only some files changed in this PR. Our checkstyle config should enforce the later based on https://checkstyle.sourceforge.io/checks/blocks/rightcurly.html

The following may not be relevant for this PR, but I tripped over it while looking into try/catch formatting.

The config copied from grails-forge appears to be using com.puppycrawl.tools:checkstyle:8.33 vs 10.26.1. https://docs.gradle.org/current/userguide/checkstyle_plugin.html

And the spotless config appears to only be checking the license headers, since we have not applied a formatter.
https://github.com/diffplug/spotless/tree/main/plugin-gradle#java

Copy link
Contributor

@matrei matrei left a comment

Choose a reason for hiding this comment

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

I have not gone through all files, but I found enough issues to start discussion/iteration.

Disclaimer: I have not used Checkstyle or Spotless before.

Comment on lines 39 to 48
checkstyle {
configFile = project.rootProject.layout.projectDirectory.file('etc/config/checkstyle/checkstyle.xml').asFile
configDirectory = project.rootProject.layout.projectDirectory.dir('etc/config/checkstyle').asFile

// Per submodule
maxErrors = 1
maxWarnings = 10

showViolations = true
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion:

extensions.configure(CheckstyleExtension) {
    // Explicit `it` required in extension configuration
    it.configFile = rootProject.layout.projectDirectory.file('etc/config/checkstyle/checkstyle.xml').asFile
    it.configDirectory = project.rootProject.layout.projectDirectory.dir('etc/config/checkstyle').asFile
    it.maxErrors = 1
    it.maxWarnings = 10
    it.showViolations = true
}

if (tasks.names.contains('checkstyleTest')) {
tasks.named('checkstyleTest').configure {
it.group = 'verification'
it.enabled = false
Copy link
Contributor

Choose a reason for hiding this comment

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

Add comment why we have chosen to disable checkstyleTest?

return params != null && params.length > 0
&& params[0].getType() != new ClassNode(Object[].class)
&& params[0].getType() != new ClassNode(Object.class);
return params != null && params.length > 0 && params[0].getType() != new ClassNode(Object[].class) && params[0].getType() != new ClassNode(Object.class);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is worse.

final BlockStatement newMethodCode = initializeActionParameters(
controllerClassNode, closureProperty, closureProperty.getName(),
parameters, source, context);
final BlockStatement newMethodCode = initializeActionParameters(controllerClassNode, closureProperty, closureProperty.getName(), parameters, source, context);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use multi-line as above suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

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

There are a lot of final declarations for local variables. Can we remove these?
We could also use var for these to make this class a lot more readable.

@@ -484,12 +444,10 @@ protected void addMethodToInvokeClosure(ClassNode controllerClassNode,
}
}

final MethodCallExpression methodCallExpression = new MethodCallExpression(
closureExpression, "call", closureInvocationArguments);
final MethodCallExpression methodCallExpression = new MethodCallExpression(closureExpression, "call", closureInvocationArguments);
Copy link
Contributor

Choose a reason for hiding this comment

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

Multi-line?

newMethodCode.addStatement(new ExpressionStatement(applyMethodTarget(methodCallExpression, Closure.class, Object.class)));

final MethodNode methodNode = new MethodNode(closureProperty.getName(), Modifier.PUBLIC,
new ClassNode(Object.class), ZERO_PARAMETERS, EMPTY_CLASS_ARRAY, newMethodCode);
final MethodNode methodNode = new MethodNode(closureProperty.getName(), Modifier.PUBLIC, new ClassNode(Object.class), ZERO_PARAMETERS, EMPTY_CLASS_ARRAY, newMethodCode);
Copy link
Contributor

Choose a reason for hiding this comment

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

Multi-line, non-final, var?

@matrei
Copy link
Contributor

matrei commented Jul 17, 2025

Could we squash the reformat commits when we are done instead of force-pushing every iteration? It's easier to follow cause and effect, and what's changed that way.

@jdaugherty
Copy link
Contributor Author

FYI: Some of these comments I'm going to commit as separate commits to make it easier to review, then I'll rebase at the end of this PR to have just 3 commits.

@jdaugherty
Copy link
Contributor Author

Could we squash the reformat commits when we are done instead of force-pushing every iteration? It's easier to follow cause and effect, and what's changed that way.

I wrote my response before I saw this. Yes, absolutely!

@jdaugherty
Copy link
Contributor Author

jdaugherty commented Jul 17, 2025

In Converter.java I don't think we want to remove the public modifiers, since it is a java file. This is the only java file, I found, where this occurred.

We have a mix of

try {
}
catch (..) {
}

and

try {
} catch (..) {
}

with only some files changed in this PR. Our checkstyle config should enforce the later based on https://checkstyle.sourceforge.io/checks/blocks/rightcurly.html

The following may not be relevant for this PR, but I tripped over it while looking into try/catch formatting.

The config copied from grails-forge appears to be using com.puppycrawl.tools:checkstyle:8.33 vs 10.26.1. https://docs.gradle.org/current/userguide/checkstyle_plugin.html

And the spotless config appears to only be checking the license headers, since we have not applied a formatter. https://github.com/diffplug/spotless/tree/main/plugin-gradle#java

Concerning Converter.java - interfaces in java no longer suggest using public. I was surprised by this change as well. I removed it based on the feedback from the tools. It seems like if that's the new java recommendation, we should go with it? https://stackoverflow.com/a/161787/3621514 - this came from the JLS recommendation

@jamesfredley @matrei do either of you have time to suggest changes to the checkstyle config and reformat incrementally? I was hoping this could be a joint effort =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

Code Standards, Reformat and Verification
5 participants