Skip to content

Preserve trailing comments when removing an import#8010

Draft
MBoegers wants to merge 3 commits into
mainfrom
mboegers/removeimport-preserve-trailing-comment
Draft

Preserve trailing comments when removing an import#8010
MBoegers wants to merge 3 commits into
mainfrom
mboegers/removeimport-preserve-trailing-comment

Conversation

@MBoegers

@MBoegers MBoegers commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

An end-of-line comment on an import line is stored in the LST as the prefix of the next import, not as a suffix of its own. RemoveImport carries a removed import's prefix forward to the following import so blank lines between groups are preserved (#3868), but it only forwarded getLastWhitespace() — any comments in that prefix were silently discarded.

Because of where comments are stored, removing an import touched comments that belong to neighbouring lines:

  • the trailing comment of the line before the removed import was dropped (it lives in the removed import's prefix);
  • standalone / commented-out lines before the removed import were dropped (same reason);
  • the removed import's own trailing comment (which lives in the next import's prefix) dangled onto the previous line, misattributing it.

The most visible casualty was a Kotlin // ktlint-disable suppression on a wildcard import being removed when the import on the next line was renamed, which then broke the build with a fresh lint failure.

Fix

When removing an import:

  • forward the removed import's prefix comments (the previous line's trailing comment and any standalone / commented-out lines) onto the next import, still preserving blank lines; and
  • drop only the removed import's own end-of-line comment — the first comment of the next import's prefix when it sits on the same line as the removed import (no newline precedes it).

Behaviour, by example

Each example removes java.util.ArrayList. A comment is kept unless it described the removed line itself.

1. The removed import's own end-of-line comment → dropped (it described the import that's gone):

 import java.util.List;
-import java.util.ArrayList; // explains the removed import
 import java.util.UUID;

2. The previous line's end-of-line comment → preserved (e.g. a ktlint suppression on a wildcard import — the original bug):

 import java.util.List; // ktlint-disable no-wildcard-imports
-import java.util.ArrayList;
 import java.util.UUID;

3. A standalone / commented-out line before the removed import → preserved:

 import java.util.List;
 // import java.util.Date
-import java.util.ArrayList;
 import java.util.UUID;

4. The following line's end-of-line comment → preserved:

 import java.util.List;
-import java.util.ArrayList;
 import java.util.UUID; // explains UUID

Last import removed

When the last import is removed there is no following import to carry its prefix onto — the follower is the first class declaration (or the EOF when there are none). The same rules apply, except the class keeps its own spacing (an import-group blank line is not pushed in front of it). So a preceding line's comment is still preserved:

 import java.util.List; // ktlint-disable no-wildcard-imports
-import java.util.ArrayList;

 class A {
 }

…and the removed import's own comment is still dropped (rather than dangling onto the previous line):

 import java.util.List;
-import java.util.ArrayList; // explains the removed import

 class A {
 }

Tests

RemoveImportTest (rewrite-java-test): dropsTrailingCommentOfRemovedImport, preservesTrailingCommentOnLineBeforeRemovedImport, preservesTrailingCommentAndBlankLineAfterRemovedImport, preservesCommentedOutLineBeforeRemovedImport, preservesTrailingCommentOnLineAfterRemovedImport, and for the last-import case dropsTrailingCommentOfRemovedLastImport, preservesPreviousLineCommentWhenLastImportRemoved, preservesCommentedOutLineWhenLastImportRemoved.

RemoveImportTest (rewrite-kotlin): preservesKtlintSuppressionCommentOnLineBeforeRemovedImport, preservesPreviousLineCommentWhenLastImportRemoved.

Context: moderneinc/customer-requests#2437.

An end-of-line comment on an import is stored in the LST as the prefix of
the *next* import. RemoveImport carried a removed import's prefix forward
to keep blank lines between groups (#3868) but only forwarded
getLastWhitespace() — comments in the prefix were discarded. As a result the
trailing comment of the line *before* a removed import (and standalone /
commented-out lines) were dropped, while the removed import's *own* trailing
comment dangled onto the previous line.

Forward the previous line's trailing comment and standalone comments onto the
next import (preserving blank lines), and drop only the removed import's own
end-of-line comment, which described the now-removed import.

Context: moderneinc/customer-requests#2437
Signed-off-by: Merlin Bögershausen <merlin.boegershausen@rwth-aachen.de>
@MBoegers MBoegers force-pushed the mboegers/removeimport-preserve-trailing-comment branch from 64fc90c to 6ca21f8 Compare June 15, 2026 08:48
@MBoegers MBoegers requested a review from timtebeek June 15, 2026 09:00
MBoegers and others added 2 commits June 15, 2026 11:37
When the last import is removed there is no following import to carry its
prefix onto, so a preceding line's trailing comment or a commented-out line
was lost, and the removed import's own trailing comment dangled onto the
previous line. Forward the prefix to the first class declaration (or the EOF
when there are none) instead, reusing the same merge logic. The first class
keeps its own spacing so import-group blank lines are not pushed onto it.

Addresses review feedback on the last-import edge case.

Signed-off-by: Merlin Bögershausen <merlin.boegershausen@rwth-aachen.de>

@sambsnyd sambsnyd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we're going to try and be smarter about this we should probably differentiate between "line ending" and "preceding line" comments .

import com.foo.Bar; // This comment is probably about Bar


// This comment is probably about Baz
import com.foo.Baz;

In this example the two comments are both part of the import Baz prefix, but should probably be treated separately.

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.

3 participants