OpenRewrite

Automated Refactorings

Merlin Bögershausen / @mboegie

Was ist Refactoring?

Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which "too small to be worth doing".
— Fowler

Multiline Strings

Klassisch

String query =
  "SELECT * \n" +
  "FROM my_table\n" +
  "WHERE something = 1;";

Java 15+

String query = """
  SELECT *
  FROM my_table
  WHERE something = 1;
  """;

Framework Migration

Spring Boot 2.7 Migration Guide
.stretch
.stretch
moderne logo

Instruction

Tune Table 1980s L
Tune Table 1980s R

Recipes

class T {
  java.util.List<String> list;
}
import java.util.List;
class T {
  List<String> list;
}

Refactor all in one

mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
  -Drewrite.activeRecipes=org.openrewrite.java.\
    ShortenFullyQualifiedTypeReferences
rewrite-maven-plugin:5.20.0:run (default-cli) @ spring-petclinic
Using active recipe(s) [o.o.h.ShortenFullyQualifiedTypeReferences]
Using active styles(s) []
Validating active recipes...
Project [petclinic] Resolving Poms...
Project [petclinic] Parsing source files
Running recipe(s)...
Changes have been made to o/s/e/petclinic/vet/Vet.java by:
   o.o.j.ShortenFullyQualifiedTypeReferences
Please review and commit the results.

Continuous Refactoring

Configure as part of your project

<!-- project/build/plugins -->
<plugin>
  <groupId>org.openrewrite.maven</groupId>
  <artifactId>rewrite-maven-plugin</artifactId>
  <version>5.17.1</version> <!-- keep this up to date 😉 -->
  <configuration>
    <activeRecipes>
      <recipe>
  org.openrewrite.java.ShortenFullyQualifiedTypeReferences
      </recipe>
    </activeRecipes>
  </configuration>
</plugin>

Recipes Catalog

background

Let’s Start 🏃🏾‍🏃🏾‍

Screenshot

Activate Recipe

Migrate to Spring Boot 3.2 (from 2.0 😱🤯)

-Drewrite.recipeArtifactCoordinates=\
  org.openrewrite.recipe:rewrite-spring:RELEASE \
-Drewrite.activeRecipes=\
  org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2

Refactor!

Changes have been made to src/main/java/o/s/s/p/v/Vet.java by:
 o.o.j.spring.boot3.UpgradeSpringBoot_3_2
  o.o.j.spring.boot3.UpgradeSpringBoot_3_1
   o.o.j.spring.boot3.UpgradeSpringBoot_3_0
    o.o.j.migrate.UpgradeToJava17
     o.o.j.migrate.Java8toJava11
      o.o.j.migrate.JavaVersion11
       o.o.j.migrate.UpgradeJavaVersion: {version=11}
     o.o.j.migrate.JavaVersion17
      o.o.j.migrate.UpgradeJavaVersion: {version=17}
    o.o.j.migrate.jakarta.JavaxMigrationToJakarta

Write your own

Declarative

Reward

If it can be declarative,

do it declarative!

Declarative Example

class SomeCallee {
  @java.lang.SuppressWarnings("deprecation") // 🤡
  void doIllegalStuff() {
    someService.oldOperation(); // ok..
  }
}

class SomeService {
  @java.lang.Deprecated
  void oldOperation(){/*..*/}
}

Remove SuppressWarnings to generate compile time warnings

Remove Suppressions

---
type: specs.openrewrite.org/v1beta/recipe
name: io.github.mboegers.RemoveDeprecationSuppression
displayName: Remove SuppressWarnings for deprecation
recipeList:
  - org.openrewrite.java.RemoveAnnotation:
      annotationPattern: \
        '@java.lang.SuppressWarnings("deprecation")'

Result after execution

class SomeCallee {
  void doIllegalStuff() {
     someService.oldOperation();
  }
}

class SomeService { /*...*/ }
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.`

Refaster

Bildschirmfoto 2024 01 17 um 16.41.59

Projektseite: ErrorProne.info

Refaster Example

class SomeCallee {
  void doIllegalStuff() {
    someService.oldOperation(); // ⚠️
  }
}

class SomeService {
  @java.lang.Deprecated
  void oldOperation(){/*..*/}
  void betterOperation(){/*..*/}
}

switch from oldOperation to newOperation

Refaster Recipe

@RecipeDescriptor(
  name = "Replace oldOperation with betterOperation",
  description = "Replace deprecated ´oldOperation´ " +
                "with surrogate ´betterOperation´")
public static class ReplaceOldOperation {
  @BeforeTemplate
  public void oldOperation(SomeService s) {
    s.oldOperation();
  }
  @AfterTemplate
  public void newOperation(SomeService s) {
    s.betterOperation();
  }
}

Refaster Result

class SomeCallee {
  void doIllegalStuff() {
    someService.betterOperation(); // 👌
  }
}

class SomeService {
  @java.lang.Deprecated
  void oldOperation(){/*..*/}
  void betterOperation(){/*..*/}
}

What if? And how?

LST

class A {
  void test() {
    int a;
    a = 0;
  }
}
-J.CompilationUnit
 \-J.ClassDeclaration
   |-J.Identifier | "A"
   \-J.Block
     \-J.MethodDeclaration | "MethodDeclaration{A{name=test,return=void,parameters=[]}}"
       |---J.Primitive | "void"
       |---J.Identifier | "test"

Imperative Recipe

public class MakePublic extends Recipe {
  @Override
  protected JavaVisitor<ExecutionContext> getVisitor() {
    return new ChangeTypeVisitor();
  }
  public String getDisplayName() {
    return "Make Class Public";
  }
  private class MakePublicVisitor
    extends JavaVisitor<ExecutionContext> {}
}

Visitor

Bearbeite den LST mit Visitors

class JavaVisitor<P> extends TreeVisitor<J, P> {
  J visitStatement(Statement statement) {}
  J visitAnnotatedType(J.AnnotatedType annotatedType)  {}
  J visitAnnotation(J.Annotation annotation) {}
  J visitAssert(J.Assert azzert) {}
  J visitAssignment(J.Assignment assign) {}
  J visitAssignmentOperation(J.AssignmentOperation assignOp) {}
  //...
}

Visitor Implementation

new JavaIsoVisitor<ExecutionContext> {
  public J.ClassDeclaration visitClassDeclaration(
          J.ClassDeclaration cd, ExecutionContext ctx) {
    cd = super.visitClassDeclaration(cd, ctx);
    List<J.Modifier> modifiers = cd.getModifiers();
    modifiers.removeIf(
            m -> J.Modifier.Type.Private.equals(m.getType()));
    // and Protected & Public
    modifiers.add(PUBLIC_MODIFIER);
    return cd.withModifiers(modifiers);
  }
}

Styles

type: specs.openrewrite.org/v1beta/style
name: io.moderne.spring.style
styleConfigs:
  - org.openrewrite.java.style.NeedBracesStyle:
      allowSingleLineStatement: false
      allowEmptyLoopBody: true
for(int i = 0; i < 10; i++);
if(success()) return false;
for(int i = 0; i < 10; i++);
if(success()){
    return false;
}

.stretch

Abilities

powerOfRewrite

Write your Own

  1. Declarative

  2. Refaster Templates

  3. Imperative

Always test driven, see you at JavaLand

Bildschirmfoto 2024 01 16 um 13.07.09

Moderne

.stretch

Image Credits