Skip to content

Fix OneCasePerLine stranding the case keyword on its own line#1208

Merged
allevato merged 2 commits into
swiftlang:mainfrom
adityasingh2400:fix-onecaseperline-continuation-lines
May 28, 2026
Merged

Fix OneCasePerLine stranding the case keyword on its own line#1208
allevato merged 2 commits into
swiftlang:mainfrom
adityasingh2400:fix-onecaseperline-continuation-lines

Conversation

@adityasingh2400

Copy link
Copy Markdown
Contributor

Fixes #1009.

When a multi-element case declaration has its elements spread across continuation lines, OneCasePerLine left the leading newline trivia on each element's name when splitting them into separate declarations. With respectsExistingLineBreaks enabled (the default), the pretty printer honored that newline, so the case keyword ended up alone on its own line:

public enum TestEnum: Int {
  case a = 0,
    b = 1,
    c = 2
}

was reformatted to

public enum TestEnum: Int {
  case a = 0
  case
    b = 1
  case
    c = 2
}

Fix

When building each generated case declaration, drop the leading vertical whitespace (newlines and surrounding indentation) from its first element so the element stays on the same line as the case keyword. Comments attached to the element are preserved rather than dropped, so something like

case a = 1,
  // keep with b
  b = 2

still keeps the comment with b.

Testing

Added testElementsOnContinuationLinesAreMovedOntoCaseKeyword covering the reported case and testCommentBetweenElementsOnContinuationLinesIsPreserved for the comment edge case. swift test passes (915 tests, the one pre-existing skip unchanged).

When the elements of a multi-element case declaration are spread across
continuation lines (each element after a trailing comma on its own line),
splitting them into separate case declarations kept the leading newline
trivia on the element name. With respectsExistingLineBreaks enabled (the
default), the pretty printer honored that newline and emitted the case
keyword alone on one line with the element on the next, e.g.

    case
      b = 1

which is also harder to read. Drop the leading vertical whitespace from
the first element of each generated case declaration while preserving any
comments attached to it.
expected: """
enum Foo: Int {
case a = 1
case // This should stay with `b`.

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.

I think in this situation, we could do even better: Move the comment/trivia to precede the newly inserted case keyword.

Another question: What if the comment is an end-of-line comment on the previous line, like this?

enum Foo: Int {
  case a = 1,  // a comment about 'a'
    b = 2
}

In that case, since the comment is at the end of the line associated with a, it should stay on that line. Can you add a test for that as well and make sure it behaves as described?

Refine the trivia handling so a comment that documents a binding on a
continuation line moves to precede the newly inserted case keyword
instead of being stranded after it. An end-of-line comment that sits on
the previous binding's line now stays attached to that binding rather
than migrating to the new case.

Previously the comment was left as the leading trivia of the element, so
it ended up between case and the binding name. The split now hoists any
documentation comment ahead of the case keyword. Separately, removing a
trailing comma used to discard the comma's trailing trivia, which lost
an end-of-line comment like `case a = 1,  // about a`. That trivia is
now preserved on the element so the comment keeps its place.

Adds tests for the hoisted leading comment, the retained end-of-line
comment, and the combination of both on a single split.
@adityasingh2400

Copy link
Copy Markdown
Contributor Author

Good calls on both, thanks. I pushed a revision.

For the documentation comment that lives on its own continuation line, I now hoist it so it precedes the newly inserted case keyword instead of leaving it stranded between case and the binding. So your earlier example splits to:

case a = 1
// This should stay with `b`.
case b = 2

For the end-of-line comment case you raised, that comment sits in the trailing trivia of the previous binding's comma, and the rule was dropping it when it stripped the comma. I now carry that trailing trivia over onto the element before removing the comma, so it stays on the same line as the binding it follows:

case a = 1,  // a comment about 'a'
  b = 2

becomes

case a = 1  // a comment about 'a'
case b = 2

I added a test for that (testEndOfLineCommentStaysWithItsElement), updated the earlier test to assert the comment now moves ahead of case, and added one more covering both kinds of comment on a single split so they each end up in the right place. swift test --filter OneCasePerLine passes locally and the changed files lint clean under --strict.

@allevato allevato 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.

Thanks!

@allevato allevato merged commit fffd8df into swiftlang:main May 28, 2026
30 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.

OneCasePerLine rule doesn't format properly with existing line breaks

2 participants