Skip to content

Fix: sort members treats record declarations as types#2942

Draft
jamesepatrick wants to merge 1 commit into
diffplug:mainfrom
jamesepatrick:main
Draft

Fix: sort members treats record declarations as types#2942
jamesepatrick wants to merge 1 commit into
diffplug:mainfrom
jamesepatrick:main

Conversation

@jamesepatrick
Copy link
Copy Markdown

@jamesepatrick jamesepatrick commented May 20, 2026

See #2941

ASTNode.RECORD_DECLARATION was missing from the switch statement in DefaultJavaElementComparator.category(), causing nested record declarations to fall through to the return 0 fallback instead of returning TYPE_INDEX. This placed records in the wrong sort category (near methods) rather than with other type declarations (T).

The same node type was also missing from the secondary sort switch in compare(), which handles name-based ordering within a category.

I did not add any tests for this are there were not tests for the Eclipse formatter.

ASTNode.RECORD_DECLARATION was missing from the switch statement in
DefaultJavaElementComparator.category(), causing nested record
declarations to fall through to the `return 0` fallback instead of
returning TYPE_INDEX. This placed records in the wrong sort
category (near methods) rather than with other type declarations (T).

The same node type was also missing from the secondary sort switch in
compare(), which handles name-based ordering within a category.

I did not add any tests for this are there were not tests for the
Eclipse formatter.
@jamesepatrick jamesepatrick changed the title Fix: sort members treats record declarations as types (#2941) Fix: sort members treats record declarations as types May 20, 2026
@jamesepatrick jamesepatrick marked this pull request as draft May 20, 2026 13:52
@jamesepatrick
Copy link
Copy Markdown
Author

Please hold. I believe my gotcha may be incorrect. Verifying results.

@jamesepatrick
Copy link
Copy Markdown
Author

jamesepatrick commented May 21, 2026

My theory of this bug was incorrect. I had assumed that because this was hitting return 0; // should never happen code that this was the source of the issue.

The TL;DR is the EclipseJdtSortMembers leverages eclipse's jdt internals to handle sorting. The ComplicationUnit use by the sort needs to have JavaProject to function; we presently stub in an empty project. Since the Java compiler & compliance version is not defined it looks to be defaulting version 1.8 (see docs). This would also explain why the formatter also appears to have issues with sealed classes.

As a quick validation I hard-coded support for java 17.

diff --git i/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtSortMembers.java w/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtSortMembers.java
index a50c41a45..f40f096ae 100644
--- i/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtSortMembers.java
+++ w/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtSortMembers.java
@@ -28,6 +28,7 @@ import org.eclipse.jdt.core.IBuffer;
 import org.eclipse.jdt.core.IBufferChangedListener;
 import org.eclipse.jdt.core.ICompilationUnit;
 import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.IOpenable;
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.dom.AST;
@@ -220,7 +221,9 @@ public final class EclipseJdtSortMembers {
 
 		@Override
 		public Map<String, String> getOptions(boolean inheritJavaCoreOptions) {
-			return Map.of();
+			return Map.of(
+					JavaCore.COMPILER_SOURCE, JavaCore.VERSION_17,
+					JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_17);
 		}
 	}

Results in the correct sort order

public class SortMembersRecordBug {

  public record ConversionType(String convertType, String extension) {
  }

  public void aMethod() {
  }

  public void bMethod() {
  }

  public void zMethod() {
  }
}

So there are a couple of different potential options.

1. Hard code in support for latest supported version

The simplest answer would be to use JavaCore.latestSupportedJavaVersion() to use the most recent version of Java that the bundled JDT supports. Since java, to the best of my knowledge, doesn't remove language features the formatter logic for 25 should be backwards compatible with 1.4 for syntactically valid java. I am a bit worried how JDT would handle using a newer version of java than was is presently installed.

2. Let the user specify it

We could let user define the it in the configuration file. For examplepom.xml

<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <version>3.5.0</version>
  <configuration>
    <baseDir>${spotless.baseDir}</baseDir>
    <java>
      <includes>
        <include>**/*.java</include>
      </includes>
      <eclipse>
        <compilerSourceVersion>17</compilerSourceVersion> <!-- added -->
        <compilerComplianceVersion>17</compilerComplianceVersion> <!-- added -->
        <sortMembersEnabled>true</sortMembersEnabled>
        <sortMembersOrder>T,SF,SI,SM,F,I,C,M</sortMembersOrder>
        <sortMembersDoNotSortFields>true</sortMembersDoNotSortFields>
        <sortMembersVisibilityOrderEnabled>true</sortMembersVisibilityOrderEnabled>
        <sortMembersVisibilityOrder>B,V,R,D</sortMembersVisibilityOrder>
      </eclipse>
    </java>
  </configuration>
</plugin>

3. Use the version of java that the formatter is being run with

I think it would be a reasonable assumption that the version number used to run spotless is a good version to run the JavaProject as. If we want to support 1.8 and earlier then we'd need to use the System.getProperty("java.version") rather than Runtime.version().


I think the best course of action is to realistically is an all of the above.

  • Use the user specified version if provided. If no user specified version is provided default version being used to run the formatter.
  • If version is greater than JavaCore.latestSupportedJavaVersion() use JavaCore.latestSupportedJavaVersion()
  • Verify version exists in JavaCore.getAllVersions(). Then use that.

I should have time to add this tomorrow. I'm not really sure how the properties get passed around so I may forego implementing that part.

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.

1 participant