OpenRewrite とは
- ソースコードの書き換えツール
- ソースコードを Lossless Semantic Tree (LST) と呼ばれるツリー表現で解釈して検索と変換を行う
- フレームワークのバージョン移行、脆弱性パッチや大規模なソースコードリファクタリングが可能
- Gradle や Maven プラグインとして利用可能
多くは、フレームワークのバージョンアップに合わせたマイグレーションで使われることが多いと思いますが、コード整形用途であらかじめ導入しておくのが便利なのでここで紹介します。
OpenRewrite プラグイン設定
Gradle の Kotlin DSL を例にします。
build.gradle.kts
に以下のプラグインを追加します。
plugins { java id("org.openrewrite.rewrite") version "6.5.11" }
OpenRewrite による書き換えは、レシピ(やスタイル)として多数公開されており、これを build.gradle.kts
の rewrite
ブロックでアクティブにすることで利用できます。
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.kts
の rewrite
ブロックで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
が用意されており、以下の整形をまとめて適用できます。
- BlankLines
- NormalizeFormat
- NormalizeLineBreaks
- NormalizeTabsOrSpaces
- RemoveTrailingWhitespace
- Spaces
- TabsAndIndents
- WrappingAndBraces
- MinimumViableSpacing
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); } }
その他、レシピについては以下で参照することができます。
自身でレシピを作成する場合は以下のリファレンスを参照してください。