Skip to content

fix: remove unnecessary continue in empty while loops #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

juancaCeb
Copy link

@juancaCeb juancaCeb commented May 28, 2025

What's changed?

Changed EmptyBlockVisitor "visitWhileLoop" and "visitDoWhileLoop" methods so that they do not add any "continue" statement. Instead, If there is a continue statement, it should remove them.

What's your motivation?

Anything in particular you'd like reviewers to focus on?

I would like to check if it's okay to put the logic of removing the continue statement on the same class. Or if not, where should it be?

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Some suggestions could not be made:

  • src/main/java/org/openrewrite/staticanalysis/CustomImportOrder.java
    • lines 24-24

Comment on lines -51 to +56
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralWhile()) && isEmptyBlock(w.getBody())) {
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralWhile()) && w.getBody() instanceof J.Block) {
J.Block body = (J.Block) w.getBody();
w = continueStatement.apply(updateCursor(w), body.getCoordinates().lastStatement());
List<Statement> statements = body.getStatements();
if (statements.size() == 1 && statements.get(0) instanceof J.Continue) {
w = w.withBody(body.withStatements(new ArrayList<>()));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

When reading the code, it seems like it handles the case of removing an existing continue statement in while loop if it's the only statement. But does the code handle empty loops after the change? I suggest you add an explicit test case for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

In other words, there are at least two scenarios on input:

  • a while-loop which is empty
  • a while-loop which just has a continue statement and nothing else.

I think before the changes the recipe handled the first case. Your code seems to change it to handling the second case. I suggest to keep the support for case no. 1, and if you prefer we can add support for case no. 2 on top of that.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the clarifications! I do have one question though:
Originally, the behavior only added a continue statement to empty while loops. That's why I did the opposite which is to remove the continue statement. However, keeping both behaviors would introduce a conflict between them. Was the original behavior intended to do something else?

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. Sorry, my input might have been confusing. I've just taken a broader look and the things are more complex than I thought. Still I think there are things to fix and your contribution is welcome.

  • When looking at EmptyBlock, its description and name, it clearly indicates empty blocks should be removed. They are not. Instead some continue statements are added.
  • At the same time, there's a thing called EmptyBlockStyle which is a very detailed way of modelling what should happen with empty blocks.
  • As a consequence I think we need multiple variants of the EmptyBlock logic. As we need to cater for multiple behaviors.
  • BlockPolicy seems to have two values: TEXT, STATEMENT. None of them calls for removing the blocks.

My personal preference would be for the EmptyBlock recipe to remove blocks by default. And optionally do something else if instructed.

Thus I suggest:

  • keep the BlockPolicy and EmptyBlockStyle as they are,
  • change the default behavior of EmptyBlock - the default should remove the empty blocks
  • if a valid EmptyBlockStyle is passed, it should be honored accordingly

@timtebeek Can you please review the suggestion?

Copy link
Member

Choose a reason for hiding this comment

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

Also my first time looking in detail here; I propose we implement the following:

    @Nested
    class WhileLoop {
        @Test
        void emptyWhileDefault() { // Checkstyle.emptyBlock() uses `BlockPolicy` `TEXT`
            rewriteRun(
              //language=java
              java(
                """
                  public class A {
                      public void foo() {
                          while(true) {
                          }
                          do {
                          } while(true);
                      }
                  }
                  """,
                """
                  public class A {
                      public void foo() {
                          while(true) {
                              // do nothing
                          }
                          do {
                              // do nothing
                          } while(true);
                      }
                  }
                  """
              )
            );
        }

        @Test
        void whileWithContinueDefault() {
            rewriteRun(
              //language=java
              java( // No change expected
                """
                  public class A {
                      public void foo() {
                          while(true) {
                              continue;
                          }
                          do {
                              continue;
                          } while(true);
                      }
                  }
                  """
              )
            );
        }

        @Test
        void emptyWhileStyleStatement() {
            rewriteRun(
              spec -> {
                  spec.parser(JavaParser.fromJavaVersion().styles(
                    singleton(Checkstyle.defaults()
                      .withStyles(singleton(Checkstyle.emptyBlock()
                        .withBlockPolicy(EmptyBlockStyle.BlockPolicy.STATEMENT))))
                  ));
              },
              //language=java
              java(
                """
                  public class A {
                      public void foo() {
                          while(true) {
                          }
                          do {
                          } while(true);
                      }
                  }
                  """,
                """
                  public class A {
                      public void foo() {
                          while(true) {
                              continue;
                          }
                          do {
                              continue;
                          } while(true);
                      }
                  }
                  """
              )
            );
        }
    }

Copy link
Member

Choose a reason for hiding this comment

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

I do not think we can safely remove empty while blocks; I'm somewhat surprised a recipe aimed at removing empty blocks is even touching these at all, or changing their body, but the above strikes the right balance I think.

The reason I think we can not remove empty while blocks is things like the following, which are valid & should not be removed

while (input.read() != null) {} // fully consume input

@timtebeek timtebeek moved this from In Progress to Ready to Review in OpenRewrite May 30, 2025
@timtebeek timtebeek moved this from Ready to Review to In Progress in OpenRewrite May 30, 2025
Copy link
Member

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Wondering a bit about if, and which changes we should be making here; as pointed out in

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.

EmptyBlock adds unnecessary continue in empty while loops
3 participants