Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ String createRevisedSourceCode(CodeFormatter formatter, Path javaFile, String so
final IDocument workingDoc = new Document(unixSourceCode);

ImportOrderProcessor importOrderProcessor = createImportOrderProcessor();
importOrderProcessor.rewriteImportsIfAny(compilationUnit, workingDoc);
importOrderProcessor.rewriteImportsIfAny(javaFile, compilationUnit, workingDoc);

// Now format the (possibly) updated document
FormatterProcessor formatterProcessor = new FormatterProcessor(formatter);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.github.bmarwell.jfmt.commands;

import io.github.bmarwell.jfmt.imports.ImportOrderConfiguration;
import io.github.bmarwell.jfmt.imports.ImportOrderProcessorException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
Expand All @@ -27,7 +30,7 @@ public ImportOrderProcessor(ImportOrderConfiguration importOrderTokens) {
* Rewrites the imports in the provided document according to the configured order.
* If there are no imports, this method is a no-op.
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The JavaDoc for rewriteImportsIfAny method is incomplete. It should document the new javaFile parameter and the new ImportOrderProcessorException that can be thrown.

Suggested update:

/**
 * Rewrites the imports in the provided document according to the configured order.
 * If there are no imports, this method is a no-op.
 *
 * @param javaFile
 *     the path to the Java source file being processed
 * @param compilationUnit
 *     the parsed compilation unit containing the imports
 * @param workingDoc
 *     the document to modify
 * @throws BadLocationException
 *     if the import block cannot be replaced at the calculated position
 * @throws ImportOrderProcessorException
 *     if the import rewrite operation fails
 */
Suggested change
* If there are no imports, this method is a no-op.
* If there are no imports, this method is a no-op.
*
* @param javaFile
* the path to the Java source file being processed
* @param compilationUnit
* the parsed compilation unit containing the imports
* @param workingDoc
* the document to modify
* @throws BadLocationException
* if the import block cannot be replaced at the calculated position
* @throws ImportOrderProcessorException
* if the import rewrite operation fails

Copilot uses AI. Check for mistakes.
*/
public void rewriteImportsIfAny(CompilationUnit compilationUnit, IDocument workingDoc)
public void rewriteImportsIfAny(Path javaFile, CompilationUnit compilationUnit, IDocument workingDoc)
throws BadLocationException {
@SuppressWarnings("unchecked")
List<ImportDeclaration> imports = new ArrayList<>((List<ImportDeclaration>) compilationUnit.imports());
Expand All @@ -42,7 +45,7 @@ public void rewriteImportsIfAny(CompilationUnit compilationUnit, IDocument worki
List<ImportOrderGroup> groups = buildGroupsFromConfig(p);

String rendered = renderGroups(groups);
replaceImportsInDocument(compilationUnit, workingDoc, rendered);
replaceImportsInDocument(javaFile, compilationUnit, workingDoc, rendered);
}

private Partition partitionImports(List<ImportDeclaration> imports) {
Expand Down Expand Up @@ -144,7 +147,21 @@ private String renderGroups(List<ImportOrderGroup> groups) {
return sb.toString();
}

private void replaceImportsInDocument(CompilationUnit compilationUnit, IDocument workingDoc, String rendered)
/**
*
* @param javaFile
* @param compilationUnit
* @param workingDoc
* @param rendered
* the newly rendered import block
* @throws BadLocationException
Comment on lines +150 to +157
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

JavaDoc parameter descriptions are missing. The @param tags should include descriptions for what each parameter represents, not just the parameter name.

Suggested format:

/**
 * Replaces the import block in the document.
 *
 * @param javaFile
 *     the path to the Java source file being processed
 * @param compilationUnit
 *     the parsed compilation unit containing the imports
 * @param workingDoc
 *     the document being modified
 * @param rendered
 *     the newly rendered import block
 * @throws BadLocationException
 *     if the import block cannot be replaced at the calculated position
 */
Suggested change
/**
*
* @param javaFile
* @param compilationUnit
* @param workingDoc
* @param rendered
* the newly rendered import block
* @throws BadLocationException
/**
* Replaces the import block in the document.
*
* @param javaFile
* the path to the Java source file being processed
* @param compilationUnit
* the parsed compilation unit containing the imports
* @param workingDoc
* the document being modified
* @param rendered
* the newly rendered import block
* @throws BadLocationException
* if the import block cannot be replaced at the calculated position

Copilot uses AI. Check for mistakes.
*/
private void replaceImportsInDocument(
Path javaFile,
CompilationUnit compilationUnit,
IDocument workingDoc,
String rendered
)
throws BadLocationException {
@SuppressWarnings("unchecked")
List<ImportDeclaration> imports = (List<ImportDeclaration>) compilationUnit.imports();
Expand All @@ -161,8 +178,21 @@ private void replaceImportsInDocument(CompilationUnit compilationUnit, IDocument
.orElse(importStart);

workingDoc.replace(importStart, importEnd - importStart, rendered);
TextEdit importRewrite = compilationUnit.rewrite(workingDoc, null);
importRewrite.apply(workingDoc);
try {
TextEdit importRewrite = compilationUnit.rewrite(workingDoc, null);
importRewrite.apply(workingDoc);
} catch (IllegalArgumentException jdtEx) {
String message = String.format(
Locale.ROOT,
"Problem formatting file [%s]. Range: [%d-%d]. Rendered old: >>>\n%s\n<<<",
javaFile.toAbsolutePath(),
importStart,
importEnd,
workingDoc.get().substring(importStart, importEnd)
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The substring call workingDoc.get().substring(importStart, importEnd) may throw StringIndexOutOfBoundsException if importEnd exceeds the document length after the workingDoc.replace() call on line 180. This can happen because the document has already been modified before this error handling code runs.

Consider using a safer approach:

int docLength = workingDoc.get().length();
int safeEnd = Math.min(importEnd, docLength);
workingDoc.get().substring(importStart, safeEnd)

Or capture the original substring before the replace operation if that's what you intend to show in the error message.

Suggested change
workingDoc.get().substring(importStart, importEnd)
workingDoc.get().substring(importStart, Math.min(importEnd, workingDoc.get().length()))

Copilot uses AI. Check for mistakes.
);

throw new ImportOrderProcessorException(javaFile, message, jdtEx);
}
Comment on lines +184 to +195
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The exception handling for IllegalArgumentException lacks test coverage. Consider adding a test case that triggers this error path to ensure the ImportOrderProcessorException is thrown correctly with the expected error message and context.

This would help verify:

  • The exception message format is correct
  • The file path is properly included in the exception
  • The original IllegalArgumentException is wrapped as the cause

Copilot uses AI. Check for mistakes.
}

// contains all imports read from the original source file.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.bmarwell.jfmt.imports;

import java.nio.file.Path;
import java.util.StringJoiner;

Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The exception class lacks documentation. Add a JavaDoc comment describing when this exception is thrown and what it represents.

Example:

/**
 * Exception thrown when the import order processor encounters an error
 * while rewriting imports in a Java source file.
 */
public class ImportOrderProcessorException extends RuntimeException {
Suggested change
/**
* Exception thrown when the import order processor encounters an error
* while rewriting imports in a Java source file.
* <p>
* This exception provides the path of the file being processed and the underlying cause.
*/

Copilot uses AI. Check for mistakes.
public class ImportOrderProcessorException extends RuntimeException {
private final Path path;

Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The constructor lacks documentation. Add a JavaDoc comment describing the parameters.

Example:

/**
 * Creates a new import order processor exception.
 *
 * @param path the path to the Java file being processed
 * @param message the error message
 * @param cause the underlying cause of the error
 */
public ImportOrderProcessorException(Path path, String message, Throwable cause) {
Suggested change
/**
* Creates a new import order processor exception.
*
* @param path the path to the Java file being processed
* @param message the error message
* @param cause the underlying cause of the error
*/

Copilot uses AI. Check for mistakes.
public ImportOrderProcessorException(Path path, String message, Throwable cause) {
super(message, cause);
this.path = path;
}

@Override
public String toString() {
return new StringJoiner(", ", ImportOrderProcessorException.class.getSimpleName() + "[", "]")
.add("super=" + super.toString())
.add("path=" + path)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package io.github.bmarwell.jfmt.imports;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

Expand All @@ -17,11 +14,7 @@ protected String getProfileName() {

@BeforeAll
static void loadSource() throws IOException {
try (InputStream in =
ImportOrderProcessorTestBase.class.getClassLoader().getResourceAsStream("imports/MavenCore.java")) {
assertNotNull(in, "Test resource imports/MavenCore.java must exist");
source = new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
source = ImportOrderTestUtil.loadTestResource("imports/MavenCore.java");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ class EmptyConfigurationImportOrderTest {
@Test
void empty_configuration_should_place_static_imports_first() throws Exception {
// given
String source = loadTestResource("imports/AbstractCommandLike.java");
CompilationUnit cu = parseCompilationUnit(source, "AbstractCommandLike.java");
IDocument workingDoc = new Document(source);
ImportOrderTestUtil.TestResource source = loadTestResource("imports/AbstractCommandLike.java");
CompilationUnit cu = parseCompilationUnit(source.contents(), "AbstractCommandLike.java");
IDocument workingDoc = new Document(source.contents());
ImportOrderConfiguration emptyConfig = ImportOrderConfiguration.empty();

// when
new ImportOrderProcessor(emptyConfig).rewriteImportsIfAny(cu, workingDoc);
new ImportOrderProcessor(emptyConfig).rewriteImportsIfAny(source.path(), cu, workingDoc);

// then
String[] lines = workingDoc.get().split("\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
class FallbackMatchesDefaultTest {

private String source;
private ImportOrderTestUtil.TestResource source;

@BeforeEach
void setUp() {
Expand All @@ -39,23 +39,23 @@ void fallback_should_match_default_configuration() throws Exception {
}

private String processWithDefaultConfiguration() throws Exception {
CompilationUnit cu = parseCompilationUnit(source, "MixedImports.java");
IDocument workingDoc = new Document(source);
CompilationUnit cu = parseCompilationUnit(source.contents(), "MixedImports.java");
IDocument workingDoc = new Document(source.contents());

NamedImportOrder nio = NamedImportOrder.valueOf("defaultorder");
ImportOrderConfiguration config = new ImportOrderLoader().loadFromResource(nio.getResourcePath());

new ImportOrderProcessor(config).rewriteImportsIfAny(cu, workingDoc);
new ImportOrderProcessor(config).rewriteImportsIfAny(source.path(), cu, workingDoc);
return workingDoc.get();
}

private String processWithEmptyConfiguration() throws Exception {
CompilationUnit cu = parseCompilationUnit(source, "MixedImports.java");
IDocument workingDoc = new Document(source);
CompilationUnit cu = parseCompilationUnit(source.contents(), "MixedImports.java");
IDocument workingDoc = new Document(source.contents());

ImportOrderConfiguration config = ImportOrderConfiguration.empty();

new ImportOrderProcessor(config).rewriteImportsIfAny(cu, workingDoc);
new ImportOrderProcessor(config).rewriteImportsIfAny(source.path(), cu, workingDoc);
return workingDoc.get();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package io.github.bmarwell.jfmt.imports;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import io.github.bmarwell.jfmt.commands.ImportOrderProcessor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
Expand All @@ -19,31 +15,27 @@
abstract class ImportOrderProcessorTestBase {

public static final String MIXED_IMPORTS_JAVA = "MixedImports.java";
static String source;
static ImportOrderTestUtil.TestResource source;

@BeforeAll
static void loadSource() throws IOException {
try (InputStream in =
ImportOrderProcessorTestBase.class.getClassLoader().getResourceAsStream("imports/MixedImports.java")) {
assertNotNull(in, "Test resource imports/MixedImports.java must exist");
source = new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
source = ImportOrderTestUtil.loadTestResource("imports/MixedImports.java");
}

protected String runAndGetImportBlock() {
try {
// Parse the source into a CompilationUnit
CompilationUnit cu = parseCompilationUnit(source);
CompilationUnit cu = parseCompilationUnit(source.contents());

// Prepare the working document
IDocument workingDoc = new Document(source);
IDocument workingDoc = new Document(source.contents());

// Load tokens for the given profile
NamedImportOrder nio = NamedImportOrder.valueOf(getProfileName());
ImportOrderConfiguration tokens = new ImportOrderLoader().loadFromResource(nio.getResourcePath());

// Rewrite imports according to tokens
new ImportOrderProcessor(tokens).rewriteImportsIfAny(cu, workingDoc);
new ImportOrderProcessor(tokens).rewriteImportsIfAny(source.path(), cu, workingDoc);

// Extract and return the import block
return extractImportBlock(workingDoc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
Expand All @@ -20,6 +22,8 @@ private ImportOrderTestUtil() {
// Utility class
}

record TestResource(Path path, String contents) {}

Comment on lines +25 to +26
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The TestResource record lacks documentation. Add a JavaDoc comment explaining its purpose and the meaning of its components.

Example:

/**
 * Represents a loaded test resource with its path and contents.
 *
 * @param path the resource path relative to the classpath
 * @param contents the file contents as a string
 */
record TestResource(Path path, String contents) {}
Suggested change
record TestResource(Path path, String contents) {}

Copilot uses AI. Check for mistakes.
/**
* Loads a test resource file as a string.
*
Expand All @@ -29,12 +33,12 @@ private ImportOrderTestUtil() {
* @throws IllegalStateException
* if the resource cannot be loaded
*/
static String loadTestResource(String resourcePath) {
static TestResource loadTestResource(String resourcePath) {
try (InputStream in = ImportOrderTestUtil.class.getClassLoader().getResourceAsStream(resourcePath)) {
if (in == null) {
throw new IllegalStateException("Test resource not found: " + resourcePath);
}
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
return new TestResource(Paths.get(resourcePath), new String(in.readAllBytes(), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new IllegalStateException("Failed to load test resource: " + resourcePath, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.bmarwell.jfmt.imports;

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

final class Issue164Test extends ImportOrderProcessorTestBase {

@BeforeAll
static void loadSource() {
source = ImportOrderTestUtil.loadTestResource("imports/Issue164TestDatasource.java");
}

@Override
protected String getProfileName() {
return "defaultorder";
}

@Test
void is_correctly_formatted() {
// given
String expected = """
import invalid.tool.dao.JpaUserDao;
import invalid.tool.dao.UserDao;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Parameter;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaQuery;
import java.util.function.Supplier;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.jpa.EJBQueryImpl;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Session;""";

// when
String actual = runAndGetImportBlock();

// then
assertTrue(actual.startsWith(expected), "Import block should have been as expected but was: " + actual);
}
}
36 changes: 36 additions & 0 deletions cli/src/test/resources/imports/Issue164TestDatasource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package invalid.tool.test.junit5;

import invalid.tool.dao.UserDao;
import invalid.tool.dao.JpaUserDao;

import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.jpa.EJBQueryImpl;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Session;

import java.util.function.Supplier;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Parameter;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaQuery;

final class Issue164TestDatasource implements TestDataSource {
@Override
public <RET> RET executeInTransaction(final Supplier<RET> function) {
return executeInTransaction(function, this.em);
}

@Override
public void executeInTransaction(final Runnable function) {
throw new UnsupportedOperationException("removed for testing.");
}

private static <RET> RET executeInTransaction(final Supplier<RET> function, final EntityManager entityManager) {
throw new UnsupportedOperationException("removed for testing.");
}

}
Loading