@@ -212,18 +212,28 @@ public Void visitIdentifier(IdentifierTree node, Void aVoid) {
212212 }
213213 }
214214 }
215-
216215 public static String removeUnusedImports (final String contents ) throws FormatterException {
217216 Context context = new Context ();
218217 JCCompilationUnit unit = parse (context , contents );
219- if (unit == null ) {
220- // error handling is done during formatting
221- return contents ;
222- }
223218 UnusedImportScanner scanner = new UnusedImportScanner (JavacTrees .instance (context ));
224219 scanner .scan (unit , null );
225- return applyReplacements (
220+ String s = applyReplacements (
226221 contents , buildReplacements (contents , unit , scanner .usedNames , scanner .usedInJavadoc ));
222+
223+ // Normalize newlines while preserving important blank lines
224+ String sep = Newlines .guessLineSeparator (contents );
225+
226+ // Ensure exactly one blank line after package declaration
227+ s = s .replaceAll ("(?m)^package .+" + sep + "\\ s+" + sep , "package $1" + sep + sep );
228+
229+ // Ensure exactly one blank line between last import and class declaration
230+ s = s .replaceAll ("(?m)import .+" + sep + "\\ s+" + sep + "(?=class|interface|enum|record)" ,
231+ "import $1" + sep + sep );
232+
233+ // Remove multiple blank lines elsewhere in imports section
234+ s = s .replaceAll ("(?m)^import .+" + sep + "\\ s+" + sep + "(?=import)" , "import $1" + sep );
235+
236+ return s ;
227237 }
228238
229239 private static JCCompilationUnit parse (Context context , String javaInput )
@@ -233,8 +243,7 @@ private static JCCompilationUnit parse(Context context, String javaInput)
233243 Options .instance (context ).put ("--enable-preview" , "true" );
234244 Options .instance (context ).put ("allowStringFolding" , "false" );
235245 JCCompilationUnit unit ;
236- JavacFileManager fileManager = new JavacFileManager (context , true , UTF_8 );
237- try {
246+ try (JavacFileManager fileManager = new JavacFileManager (context , true , UTF_8 )){
238247 fileManager .setLocation (StandardLocation .PLATFORM_CLASS_PATH , ImmutableList .of ());
239248 } catch (IOException e ) {
240249 // impossible
@@ -284,11 +293,21 @@ private static RangeMap<Integer, String> buildReplacements(
284293 int endPosition = importTree .getEndPosition (unit .endPositions );
285294 endPosition = max (CharMatcher .isNot (' ' ).indexIn (contents , endPosition ), endPosition );
286295 String sep = Newlines .guessLineSeparator (contents );
296+
297+ // Check if there's an empty line after this import
298+ boolean hasEmptyLineAfter = false ;
299+ if (endPosition + sep .length () * 2 <= contents .length ()) {
300+ String nextTwoLines = contents .substring (endPosition , endPosition + sep .length () * 2 );
301+ hasEmptyLineAfter = nextTwoLines .equals (sep + sep );
302+ }
303+
287304 if (endPosition + sep .length () < contents .length ()
288305 && contents .subSequence (endPosition , endPosition + sep .length ()).toString ().equals (sep )) {
289306 endPosition += sep .length ();
290307 }
291- if (size == 1 || importTree != lastImport ) {
308+
309+ // If this isn't the last import and there's an empty line after, preserve it
310+ if ((size == 1 || importTree != lastImport ) && !hasEmptyLineAfter ) {
292311 while (endPosition + sep .length () <= contents .length ()
293312 && contents .regionMatches (endPosition , sep , 0 , sep .length ())) {
294313 endPosition += sep .length ();
@@ -298,7 +317,6 @@ private static RangeMap<Integer, String> buildReplacements(
298317 }
299318 return replacements ;
300319 }
301-
302320 private static String getSimpleName (JCTree importTree ) {
303321 return getQualifiedIdentifier (importTree ).getIdentifier ().toString ();
304322 }
0 commit comments