Skip to content

Add shortenFullyQualifiedTypes step (fixes #2945) - #3005

Merged
nedtwigg merged 10 commits into
diffplug:mainfrom
maxandersen:removeFullyQualifiedNames
Aug 17, 2026
Merged

Add shortenFullyQualifiedTypes step (fixes #2945)#3005
nedtwigg merged 10 commits into
diffplug:mainfrom
maxandersen:removeFullyQualifiedNames

Conversation

@maxandersen

@maxandersen maxandersen commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

headsup - I used llm to generate this; first time contributing to this area but did a quick read and seemed to match similar operations and been using it in my own workflows so it at least works for my usecases :)

Summary

Adds a new Java formatter step that replaces fully qualified type names with simple names and adds the required imports. Designed to clean up LLM-generated code and other sources that use inline FQNs unnecessarily.

Before

public class UserService {
    private final java.util.Map<String, java.util.List<String>> cache = new java.util.HashMap<>();
    public java.util.List<String> getUsers(java.util.function.Predicate<String> filter) throws java.io.IOException {
        java.util.List<String> result = new java.util.ArrayList<>();
        return result;
    }
}

After

import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;

public class UserService {
    private final Map<String, List<String>> cache = new HashMap<>();
    public List<String> getUsers(Predicate<String> filter) throws IOException {
        List<String> result = new ArrayList<>();
        return result;
    }
}

Implementation

  • Uses JavaParser for AST-based type identification (no regex false positives from strings, comments, or non-type contexts)
  • Position-based text replacement preserves original formatting exactly
  • Configured with BLEEDING_EDGE language level to support Java 14–25 syntax
  • Deduplicates AST visits (JavaParser visits instanceof pattern nodes twice)

Safety

Skips rewriting when:

  • Two different FQNs map to the same simple name (conflict)
  • An existing import already claims the simple name for a different type
  • The file can't be parsed (returns input unchanged)
  • java.lang.* types (shortened without import)
  • Same-package types (shortened without import)

Usage

Gradle:

spotless {
    java {
        shortenFullyQualifiedTypes()
        importOrder()
        removeUnusedImports()
    }
}

Maven:

<java>
    <shortenFullyQualifiedTypes/>
    <importOrder/>
    <removeUnusedImports/>
</java>

Tests

16 tests covering: basic shortening, conflict detection, existing import conflicts, java.lang handling, same-package handling, idempotency, and Java 14–25 syntax (instanceof patterns, switch patterns, records, sealed classes, text blocks, var+generics, lambda casts).

Fixes #2945

Adds a new Java formatter step that replaces fully qualified type names
with simple names and adds the required imports. For example:

    java.util.List<String> items = new java.util.ArrayList<>();

becomes:

    import java.util.List;
    import java.util.ArrayList;
    ...
    List<String> items = new ArrayList<>();

Uses JavaParser for AST-based type identification — no false positives
from strings, comments, or non-type contexts. Position-based text
replacement preserves original formatting.

Safety: skips when simple names conflict (two FQNs → same short name),
when an existing import claims the name for a different type, and when
the file can't be parsed. java.lang and same-package types are shortened
without adding imports.

Tested with Java 14–25 syntax: instanceof pattern matching, switch
patterns, records, sealed classes, text blocks, var with generics,
and lambda casts (16 tests).

Wired into both Gradle (shortenFullyQualifiedTypes()) and Maven
(<shortenFullyQualifiedTypes/>), designed to run before importOrder()
and removeUnusedImports().

New files:
- lib/.../java/ShortenFullyQualifiedTypesStep.java (step class)
- lib/.../glue/javaparser/ShortenQualifiedTypesFormatterFunc.java (glue)
- plugin-maven/.../java/ShortenFullyQualifiedTypes.java (Maven factory)
- testlib/.../java/ShortenFullyQualifiedTypesStepTest.java (16 tests)

Modified:
- plugin-gradle/.../JavaExtension.java (+shortenFullyQualifiedTypes())
- plugin-maven/.../java/Java.java (+addShortenFullyQualifiedTypes())
@maxandersen

Copy link
Copy Markdown
Contributor Author

Any luck to get this reviewed/merged ?

@nedtwigg

Copy link
Copy Markdown
Member

This looks great! It needs changelog entries (3 of them), and also an entry in the root README.md listing of steps (note that there is a magic markdown comment that generates the table).

nedtwigg and others added 6 commits August 16, 2026 15:45
…edTypes

Adds the three changelog entries (lib, plugin-gradle, plugin-maven) and a
row in the root README's freshmark-generated feature matrix, updating both
the freshmark source block and the generated table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a section to plugin-gradle/README.md and plugin-maven/README.md next
to the other import-management steps, with a before/after example and the
cases where a name is deliberately left fully-qualified. Also lists the
step in each plugin's main Java config block, ahead of importOrder, since
new imports are appended unsorted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Java TOC line in both plugin READMEs listed the formatters but none of
the import-management steps. Adds shortenFullyQualifiedTypes along with the
four that were already undocumented in the TOC: removeUnusedImports,
forbidWildcardImports, expandWildcardImports, and forbidModuleImports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes two unused imports (java.util.Optional, StepHarness), updates the
copyright headers to 2025-2026, and applies the project's eclipse format
to the new files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nedtwigg

Copy link
Copy Markdown
Member

I wanna publish a new version today so I'll make the changes above myself. Didn't realize how much demand for this feature there was!

nedtwigg and others added 2 commits August 16, 2026 16:56
SpotBugs (SIC_INNER_SHOULD_BE_STATIC_ANON) failed :lib:spotbugsJavaParser
because the anonymous VoidVisitorAdapter captured the enclosing
ShortenQualifiedTypesFormatterFunc instance without ever using it. Lift it
into a named static class taking its state as constructor args, matching
ExpandWildcardsFormatterFunc.CollectImportedTypesVisitor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nedtwigg
nedtwigg merged commit 8b57c01 into diffplug:main Aug 17, 2026
20 checks passed
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.

Add formatter step to shorten unnecessary fully qualified class names

2 participants