大規模なコード書き換えツール OpenRewrite の使い方


OpenRewrite とは

  • ソースコードの書き換えツール
  • ソースコードを Lossless Semantic Tree (LST) と呼ばれるツリー表現で解釈して検索と変換を行う
  • フレームワークのバージョン移行、脆弱性パッチや大規模なソースコードリファクタリングが可能
  • Gradle や Maven プラグインとして利用可能

多くは、フレームワークのバージョンアップに合わせたマイグレーションで使われることが多いと思いますが、コード整形用途であらかじめ導入しておくのが便利なのでここで紹介します。

github.com


OpenRewrite プラグイン設定

Gradle の Kotlin DSL を例にします。

build.gradle.kts に以下のプラグインを追加します。

plugins {
    java
    id("org.openrewrite.rewrite") version "6.5.11"
}

OpenRewrite による書き換えは、レシピ(やスタイル)として多数公開されており、これを build.gradle.ktsrewrite ブロックでアクティブにすることで利用できます。

rewrite {
    activeRecipe("org.openrewrite.java.format.AutoFormat")
}

利用可能なレシピと、有効化されたレシピは ./gradlew rewriteDiscover とすることで一覧できます。

rewrite ブロックにレシピを定義するだけでも利用できますが、多くの場合は詳細な設定が必要なため、YAMLファイルで定義したものを、activeRecipe で有効化することになります。

build.gradle.kts と同じディレクトリに rewrite.yml を作成し、以下のようにレシピを定義します。

type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyFormat
recipeList:
  - org.openrewrite.java.OrderImports

build.gradle.ktsrewrite ブロックでYAMLで定義した名前を参照して有効化します。

rewrite {
    activeRecipe("com.example.MyFormat")
}


OpenRewrite プラグインの実行

import 宣言の並び替えを行う org.openrewrite.java.OrderImports を実行してみましょう。

build.gradle.kts と同じディレクトリに rewrite.yml を以下のように作成します。

type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyFormat
recipeList:
  - org.openrewrite.java.OrderImports

build.gradle.kts は以下のように定義します。

plugins {
    java
    id("org.openrewrite.rewrite") version "6.5.11"
}
...
rewrite {
    activeRecipe("com.example.MyFormat")
}

discover コマンドを実行すれば、定義したレシピが Active Recipes としてリストされていることが確認できます。

$ ./gradlew rewriteDiscover

Available Recipes:
    org.openrewrite.DeleteSourceFiles
    org.openrewrite.FindCollidingSourceFiles
    ...

Available Styles:
    org.openrewrite.java.IntelliJ
    ...

Active Styles:

Active Recipes:
    com.example.MyFormat


以下のコマンドでドライランを実行できます。

$ ./gradlew rewriteDryRun

app/build/reports/rewrite/rewrite.patch にパッチファイルが作成されます。

diff --git a/app/src/main/java/rewrite/App.java b/app/src/main/java/rewrite/App.java
index f354572..10c2704 100755
--- a/app/src/main/java/rewrite/App.java
+++ b/app/src/main/java/rewrite/App.java
@@ -3,10 +3,10 @@ org.openrewrite.config.CompositeRecipe
  */
 package rewrite;
 
-import java.math.BigDecimal;
-import java.util.Objects;
-import java.text.DecimalFormat;
 import java.io.Serializable;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.Objects;
 
 public class App {
     public String getGreeting() {

変更内容は以下で反映できます。

$ git apply app\build\reports\rewrite\rewrite.patch

直接変更を反映する場合は、単に以下を実行すればコードの書き換えが完了します。

$ ./gradlew rewriteRun

git diff などで変更点を確認し、問題なければコミットという流れになります。


ライセンスヘッダの追加

org.openrewrite.java.AddLicenseHeader でライセンスヘッダの追加ができます。

type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyFormat
recipeList:
  - org.openrewrite.java.AddLicenseHeader:
      licenseText: |-
        Copyright ${CURRENT_YEAR} the original author or authors.
        <p>
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
        <p>
        https://www.apache.org/licenses/LICENSE-2.0
        <p>
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.

${CURRENT_YEAR} は現在年に展開されます。

なお、既になんらかのブロックコメントが存在する場合は追加及び変更は行われません。

その他整形

Java コードの整形は org.openrewrite.java.format.AutoFormat が用意されており、以下の整形をまとめて適用できます。

org.openrewrite.java.format.AutoFormat に加え、その他の整形を含めると、以下のような定義が可能です。

type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyFormat
recipeList:
  - org.openrewrite.java.format.AutoFormat
  - org.openrewrite.java.format.EmptyNewlineAtEndOfFile  # 最終行を改行で終わる
  - org.openrewrite.java.RemoveUnusedImports             # 未使用インポートの削除
  - org.openrewrite.java.OrderImports                    # インポートの並び替え
  - org.openrewrite.java.format.SingleLineComments       # `//foo` -> `// foo`
  - org.openrewrite.java.format.RemoveTrailingWhitespace # 行末のスペース削除
  - org.openrewrite.java.AddLicenseHeader:
      licenseText: |-
        Copyright ${CURRENT_YEAR} the original author or authors.
        <p>
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
        <p>
        https://www.apache.org/licenses/LICENSE-2.0
        <p>
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.

例えば以下のコードは

package rewrite;
import java.lang.Math;
import java.util.Objects;
public class App {
  public String getGreeting(){return "Hello World!";}

    public static void main(String[] args) {
    System.out.println(new App().getGreeting());
    }
int add(int x, int y){ return Math.addExact(x, y);
}
}

以下のように整形されます。

/*
 * Copyright 2023 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package rewrite;

import java.lang.Math;

public class App {
  public String getGreeting() {
    return "Hello World!";
  }

  public static void main(String[] args) {
    System.out.println(new App().getGreeting());
  }

  int add(int x, int y) {
    return Math.addExact(x, y);
  }
}

その他、レシピについては以下で参照することができます。

docs.openrewrite.org

自身でレシピを作成する場合は以下のリファレンスを参照してください。

docs.openrewrite.org