+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 +
+https://www.apache.org/licenses/LICENSE-2.0 +
+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.
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.*;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.text.PlainText;
+import org.openrewrite.text.PlainTextParser;
+import org.openrewrite.text.PlainTextVisitor;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class AppendToReleaseNotes extends ScanningRecipe
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+
+import java.util.List;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class AssertEqualsToAssertThat extends Recipe {
+ @Override
+ public String getDisplayName() {
+ // language=markdown
+ return "JUnit `assertEquals()` to Assertj `assertThat()`";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Use AssertJ assertThat instead of JUnit assertEquals().";
+ }
+
+ private static MethodMatcher MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertEquals(..)");
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", null),
+ new JavaIsoVisitor
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import com.yourorg.table.ClassHierarchyReport;
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class ClassHierarchy extends Recipe {
+
+ transient ClassHierarchyReport report = new ClassHierarchyReport(this);
+
+ @Override
+ public String getDisplayName() {
+ return "Class hierarchy";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Produces a data table showing inheritance relationships between classes.";
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return new JavaIsoVisitor
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import fj.data.Option;
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.*;
+import org.openrewrite.analysis.dataflow.DataFlowNode;
+import org.openrewrite.analysis.dataflow.DataFlowSpec;
+import org.openrewrite.analysis.dataflow.Dataflow;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.JavaVisitor;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.java.tree.TypeUtils;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class NoCollectionMutation extends Recipe {
+ @Override
+ public String getDisplayName() {
+ return "Prevent LST collection mutation";
+ }
+
+ @Override
+ public String getDescription() {
+ return "LST elements should always be treated as immutable, even for fields that are not protected from mutation at runtime. " +
+ "Adding or removing an element from a collection on an LST element is always a bug. " +
+ "This recipe uses Dataflow analysis to detect and put defensive copies around collection mutations.";
+ }
+
+ private static final MethodMatcher ADD_MATCHER = new MethodMatcher("java.util.List add(..)");
+ private static final MethodMatcher ADD_ALL_MATCHER = new MethodMatcher("java.util.List addAll(..)");
+ private static final MethodMatcher CLEAR_MATCHER = new MethodMatcher("java.util.List clear()");
+ private static final MethodMatcher REMOVE_MATCHER = new MethodMatcher("java.util.List remove(..)");
+ private static final MethodMatcher REMOVE_ALL_MATCHER = new MethodMatcher("java.util.List removeAll(..)");
+ private static final MethodMatcher REPLACE_MATCHER = new MethodMatcher("java.util.List replace(..)");
+ private static final MethodMatcher SET_MATCHER = new MethodMatcher("java.util.List set(..)");
+ private static final MethodMatcher SORT_MATCHER = new MethodMatcher("java.util.List sort(..)");
+ /**
+ * The "select" of a method is the receiver or target of the invocation. In the method call "aList.add(foo)" the "select" is "aList".
+ *
+ * @param cursor a stack of LST elements with parent/child relationships connecting an individual LST element to the root of the tree
+ * @return true if the cursor points to the "select" of a method invocation that is a list mutation
+ */
+ private static boolean isListMutationSelect(Cursor cursor) {
+ Object parentValue = cursor.getParentTreeCursor().getValue();
+ if (!(parentValue instanceof J.MethodInvocation)
+ || ((J.MethodInvocation) parentValue).getMethodType() == null
+ || ((J.MethodInvocation) parentValue).getSelect() != cursor.getValue()) {
+ return false;
+ }
+ JavaType.Method mt = ((J.MethodInvocation) parentValue).getMethodType();
+ return ADD_MATCHER.matches(mt) ||
+ ADD_ALL_MATCHER.matches(mt) ||
+ CLEAR_MATCHER.matches(mt) ||
+ REMOVE_MATCHER.matches(mt) ||
+ REMOVE_ALL_MATCHER.matches(mt) ||
+ REPLACE_MATCHER.matches(mt) ||
+ SET_MATCHER.matches(mt) ||
+ SORT_MATCHER.matches(mt);
+ }
+
+ private static final MethodMatcher NEW_ARRAY_LIST_MATCHER = new MethodMatcher("java.util.ArrayList
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.JavaVisitor;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.TreeVisitingPrinter;
+import org.openrewrite.java.search.UsesMethod;
+import org.openrewrite.java.tree.J;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class NoGuavaListsNewArrayList extends Recipe {
+ // These matchers use a syntax described on https://docs.openrewrite.org/reference/method-patterns
+ private static final MethodMatcher NEW_ARRAY_LIST = new MethodMatcher("com.google.common.collect.Lists newArrayList()");
+ private static final MethodMatcher NEW_ARRAY_LIST_ITERABLE = new MethodMatcher("com.google.common.collect.Lists newArrayList(java.lang.Iterable)");
+ private static final MethodMatcher NEW_ARRAY_LIST_CAPACITY = new MethodMatcher("com.google.common.collect.Lists newArrayListWithCapacity(int)");
+
+ @Override
+ public String getDisplayName() {
+ //language=markdown
+ return "Use `new ArrayList<>()` instead of Guava";
+ }
+
+ @Override
+ public String getDescription() {
+ //language=markdown
+ return "Prefer the Java standard library over third-party usage of Guava in simple cases like this.";
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(
+ // Any change to the AST made by the preconditions check will lead to the visitor returned by Recipe
+ // .getVisitor() being applied
+ // No changes made by the preconditions check will be kept
+ Preconditions.or(new UsesMethod<>(NEW_ARRAY_LIST),
+ new UsesMethod<>(NEW_ARRAY_LIST_ITERABLE),
+ new UsesMethod<>(NEW_ARRAY_LIST_CAPACITY)),
+ // To avoid stale state persisting between cycles, getVisitor() should always return a new instance of
+ // its visitor
+ new JavaVisitor
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import com.google.errorprone.refaster.annotation.AfterTemplate;
+import com.google.errorprone.refaster.annotation.BeforeTemplate;
+import org.openrewrite.java.template.RecipeDescriptor;
+
+@SuppressWarnings({"SimplifiableConditionalExpression", "unused"})
+@RecipeDescriptor(
+ name = "Simplify ternary expressions",
+ description = "Simplifies various types of ternary expressions to improve code readability."
+)
+public class SimplifyTernary { // This class should not extend Recipe; a generated class will extend Recipe instead
+
+ @RecipeDescriptor(
+ name = "Replace `booleanExpression ? true : false` with `booleanExpression`",
+ description = "Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`."
+ )
+ public static class SimplifyTernaryTrueFalse {
+
+ @BeforeTemplate
+ boolean before(boolean expr) {
+ return expr ? true : false;
+ }
+
+ @AfterTemplate
+ boolean after(boolean expr) {
+ return expr;
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `booleanExpression ? false : true` with `!booleanExpression`",
+ description = "Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`."
+ )
+ public static class SimplifyTernaryFalseTrue {
+
+ @BeforeTemplate
+ boolean before(boolean expr) {
+ return expr ? false : true;
+ }
+
+ @AfterTemplate
+ boolean after(boolean expr) {
+ // We wrap the expression in parentheses as the input expression might be a complex expression
+ return !(expr);
+ }
+ }
+}
diff --git a/src/main/java/com/yourorg/StringIsEmpty.java b/src/main/java/com/yourorg/StringIsEmpty.java
new file mode 100644
index 0000000..802c91f
--- /dev/null
+++ b/src/main/java/com/yourorg/StringIsEmpty.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+// TODO - This is a placeholder for a Refaster recipe. Implement the recipe by adding before and after annotated methods.
+// The rule should replace calls to `String.length() == 0` with `String.isEmpty()`, as well as similar variants.
+// You're done when all the tests in `StringIsEmptyTest` passes.
+public class StringIsEmpty {
+}
diff --git a/src/main/java/com/yourorg/UpdateConcoursePipeline.java b/src/main/java/com/yourorg/UpdateConcoursePipeline.java
new file mode 100644
index 0000000..263ee49
--- /dev/null
+++ b/src/main/java/com/yourorg/UpdateConcoursePipeline.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.*;
+import org.openrewrite.yaml.ChangePropertyValue;
+import org.openrewrite.yaml.YamlIsoVisitor;
+import org.openrewrite.yaml.tree.Yaml;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class UpdateConcoursePipeline extends Recipe {
+ @Override
+ public String getDisplayName() {
+ return "Update concourse pipeline";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Update the tag filter on concourse pipelines.";
+ }
+
+ @Option(displayName = "New tag filter version",
+ description = "tag filter version.",
+ example = "8.2.0")
+ String version;
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(
+ Preconditions.or(
+ new FindSourceFiles("ci/pipeline*.yml").getVisitor(),
+ new FindSourceFiles("ci/pipeline*.yaml").getVisitor()),
+ new YamlIsoVisitor
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+@NonNullApi
+@NonNullFields
+package com.yourorg;
+
+// We annotate the package to indicate that fields and methods in this package are non-null by default.
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.internal.lang.NonNullFields;
diff --git a/src/main/java/com/yourorg/table/ClassHierarchyReport.java b/src/main/java/com/yourorg/table/ClassHierarchyReport.java
new file mode 100644
index 0000000..9be0c34
--- /dev/null
+++ b/src/main/java/com/yourorg/table/ClassHierarchyReport.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg.table;
+
+import lombok.Value;
+import org.openrewrite.Column;
+import org.openrewrite.DataTable;
+import org.openrewrite.Recipe;
+
+public class ClassHierarchyReport extends DataTable
+# 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
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+
+# Include any Declarative YAML format recipes here, as per:
+# https://docs.openrewrite.org/reference/yaml-format-reference
+# These are most easily composed through the Yaml recipe builder at:
+# https://app.moderne.io/recipes/builder
+
+# Notice how we can have multiple recipes in the same file, separated by `---`
+# You can also have multiple files in `src/main/resources/META-INF/rewrite`, each containing one or more recipes.
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: com.yourorg.UseOpenRewriteNullable
+displayName: Prefer OpenRewrite Nullable
+description: Replaces JetBrains Nullable with OpenRewrite Nullable.
+recipeList:
+ - org.openrewrite.java.ChangeType:
+ oldFullyQualifiedTypeName: org.jetbrains.annotations.Nullable
+ newFullyQualifiedTypeName: org.openrewrite.internal.lang.Nullable
diff --git a/src/main/resources/META-INF/rewrite/stringutils.yml b/src/main/resources/META-INF/rewrite/stringutils.yml
new file mode 100644
index 0000000..e00e129
--- /dev/null
+++ b/src/main/resources/META-INF/rewrite/stringutils.yml
@@ -0,0 +1,31 @@
+#
+# Copyright 2024 the original author or authors.
+#
+# 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
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: com.yourorg.UseApacheStringUtils
+displayName: Use Apache `StringUtils`
+description: Replace Spring string utilities with Apache string utilities
+recipeList:
+ - org.openrewrite.java.dependencies.AddDependency:
+ groupId: org.apache.commons
+ artifactId: commons-lang3
+ version: latest.release
+ onlyIfUsing: org.springframework.util.StringUtils
+ configuration: implementation
+ - org.openrewrite.java.ChangeType:
+ oldFullyQualifiedTypeName: org.springframework.util.StringUtils
+ newFullyQualifiedTypeName: org.apache.commons.lang3.StringUtils
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..0bdae2b
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,30 @@
+
+
+
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import java.nio.file.Paths;
+
+import static org.openrewrite.test.SourceSpecs.text;
+
+class AppendToReleaseNotesTest implements RewriteTest {
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new AppendToReleaseNotes("Hello world"));
+ }
+
+ @Test
+ void createNewReleaseNotes() {
+ // Notice how the before text is null, indicating that the file does not exist yet.
+ // The after text is the content of the file after the recipe is applied.
+ rewriteRun(
+ text(
+ null,
+ """
+ Hello world
+ """,
+ spec -> spec.path(Paths.get("RELEASE.md")
+ )
+ )
+ );
+ }
+
+ @DocumentExample
+ @Test
+ void editExistingReleaseNotes() {
+ // When the file does already exist, we assert the content is modified as expected.
+ rewriteRun(
+ text(
+ """
+ You say goodbye, I say
+ """,
+ """
+ You say goodbye, I say
+ Hello world
+ """,
+ spec -> spec.path(Paths.get("RELEASE.md")
+ )
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/AssertEqualsToAssertThatTest.java b/src/test/java/com/yourorg/AssertEqualsToAssertThatTest.java
new file mode 100644
index 0000000..76f8b58
--- /dev/null
+++ b/src/test/java/com/yourorg/AssertEqualsToAssertThatTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+class AssertEqualsToAssertThatTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new AssertEqualsToAssertThat())
+ .parser(JavaParser.fromJavaVersion()
+ .classpath("junit-jupiter-api"));
+ }
+
+ @DocumentExample
+ @Test
+ void twoArgument() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class A {
+ void foo() {
+ Assertions.assertEquals(1, 2);
+ }
+ }
+ """,
+ """
+ import org.assertj.core.api.Assertions;
+
+ class A {
+ void foo() {
+ Assertions.assertThat(2).isEqualTo(1);
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void withDescription() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class A {
+ void foo() {
+ Assertions.assertEquals(1, 2, "one equals two, everyone knows that");
+ }
+ }
+ """,
+ """
+ import org.assertj.core.api.Assertions;
+
+ class A {
+ void foo() {
+ Assertions.assertThat(2).as("one equals two, everyone knows that").isEqualTo(1);
+ }
+ }
+ """
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/ClassHierarchyTest.java b/src/test/java/com/yourorg/ClassHierarchyTest.java
new file mode 100644
index 0000000..3c8a306
--- /dev/null
+++ b/src/test/java/com/yourorg/ClassHierarchyTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import com.yourorg.table.ClassHierarchyReport;
+import org.junit.jupiter.api.Test;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.openrewrite.java.Assertions.java;
+
+class ClassHierarchyTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new ClassHierarchy());
+ }
+
+ @Test
+ void basic() {
+ rewriteRun(
+ spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
+ assertThat(rows).containsExactly(new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"));
+ }),
+ //language=java
+ java(
+ """
+ class A {}
+ """
+ )
+ );
+ }
+
+ @Test
+ void bExtendsA() {
+ rewriteRun(
+ spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
+ assertThat(rows).containsExactly(
+ new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"),
+ new ClassHierarchyReport.Row("B", ClassHierarchyReport.Relationship.EXTENDS, "A"));
+ }),
+ //language=java
+ java(
+ """
+ class A {}
+ """
+ ),
+ //language=java
+ java(
+ """
+ class B extends A {}
+ """
+ )
+ );
+ }
+
+ @Test
+ void interfaceRelationship() {
+ rewriteRun(
+ spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
+ assertThat(rows).containsExactly(
+ new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"),
+ new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.IMPLEMENTS, "java.io.Serializable"));
+ }),
+ // language=java
+ java(
+ """
+ import java.io.Serializable;
+ class A implements Serializable {}
+ """
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/NoCollectionMutationTest.java b/src/test/java/com/yourorg/NoCollectionMutationTest.java
new file mode 100644
index 0000000..d459942
--- /dev/null
+++ b/src/test/java/com/yourorg/NoCollectionMutationTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+
+@SuppressWarnings({"NullableProblems", "WriteOnlyObject", "ResultOfMethodCallIgnored", "DataFlowIssue"})
+class NoCollectionMutationTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new NoCollectionMutation()).parser(JavaParser.fromJavaVersion().classpath("rewrite-core", "rewrite-java"));
+ }
+
+ @Test
+ void nonMutationIsOkay() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import org.openrewrite.ExecutionContext;
+ import org.openrewrite.java.JavaIsoVisitor;
+ import org.openrewrite.java.tree.J;
+ import org.openrewrite.internal.ListUtils;
+
+ public class ManipulateMethodArguments extends JavaIsoVisitor
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+// This is a test for the NoGuavaListsNewArrayList recipe, as an example of how to write a test for an imperative recipe.
+class NoGuavaListsNewArrayListTest implements RewriteTest {
+
+ // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests.
+ // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden
+ // per test.
+ @Override
+ public void defaults(RecipeSpec spec) {
+ // Note how we directly instantiate the recipe class here
+ spec.recipe(new NoGuavaListsNewArrayList())
+ .parser(JavaParser.fromJavaVersion()
+ .logCompilationWarningsAndErrors(true)
+ // The before/after examples are using Guava classes, so we need to add the Guava library to the classpath
+ .classpath("guava"));
+ }
+
+ @DocumentExample
+ @Test
+ void replaceWithNewArrayList() {
+ rewriteRun(
+ // There is an overloaded version or rewriteRun that allows the RecipeSpec to be customized specifically
+ // for a given test. In this case, the parser for this test is configured to not log compilation warnings.
+ spec -> spec
+ .parser(JavaParser.fromJavaVersion()
+ .logCompilationWarningsAndErrors(false)
+ .classpath("guava")),
+ // language=java
+ java(
+ """
+ import com.google.common.collect.*;
+
+ import java.util.List;
+
+ class Test {
+ List
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+// This is a test for the SimplifyTernary recipe, as an example of how to write a test for a Refaster style recipe.
+class SimplifyTernaryTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name
+ spec.recipe(new SimplifyTernaryRecipes());
+ }
+
+ @Test
+ @DocumentExample
+ void simplified() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class Test {
+ boolean trueCondition1 = true ? true : false;
+ boolean trueCondition2 = false ? false : true;
+ boolean trueCondition3 = booleanExpression() ? true : false;
+ boolean trueCondition4 = trueCondition1 && trueCondition2 ? true : false;
+ boolean trueCondition5 = !true ? false : true;
+ boolean trueCondition6 = !false ? true : false;
+
+ boolean falseCondition1 = true ? false : true;
+ boolean falseCondition2 = !false ? false : true;
+ boolean falseCondition3 = booleanExpression() ? false : true;
+ boolean falseCondition4 = trueCondition1 && trueCondition2 ? false : true;
+ boolean falseCondition5 = !false ? false : true;
+ boolean falseCondition6 = !true ? true : false;
+
+ boolean binary1 = booleanExpression() && booleanExpression() ? true : false;
+ boolean binary2 = booleanExpression() && booleanExpression() ? false : true;
+ boolean binary3 = booleanExpression() || booleanExpression() ? true : false;
+ boolean binary4 = booleanExpression() || booleanExpression() ? false : true;
+
+ boolean booleanExpression() {
+ return true;
+ }
+ }
+ """,
+ """
+ class Test {
+ boolean trueCondition1 = true;
+ boolean trueCondition2 = true;
+ boolean trueCondition3 = booleanExpression();
+ boolean trueCondition4 = trueCondition1 && trueCondition2;
+ boolean trueCondition5 = true;
+ boolean trueCondition6 = true;
+
+ boolean falseCondition1 = false;
+ boolean falseCondition2 = false;
+ boolean falseCondition3 = !booleanExpression();
+ boolean falseCondition4 = !(trueCondition1 && trueCondition2);
+ boolean falseCondition5 = false;
+ boolean falseCondition6 = false;
+
+ boolean binary1 = booleanExpression() && booleanExpression();
+ boolean binary2 = !(booleanExpression() && booleanExpression());
+ boolean binary3 = booleanExpression() || booleanExpression();
+ boolean binary4 = !(booleanExpression() || booleanExpression());
+
+ boolean booleanExpression() {
+ return true;
+ }
+ }
+ """
+ )
+ );
+ }
+
+ // It's good practice to also include a test that verifies that the recipe doesn't change anything when it shouldn't.
+ @Test
+ void unchanged() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class Test {
+ boolean unchanged1 = booleanExpression() ? booleanExpression() : !booleanExpression();
+ boolean unchanged2 = booleanExpression() ? true : !booleanExpression();
+ boolean unchanged3 = booleanExpression() ? booleanExpression() : false;
+
+ boolean booleanExpression() {
+ return true;
+ }
+ }
+ """
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/StringIsEmptyTest.java b/src/test/java/com/yourorg/StringIsEmptyTest.java
new file mode 100644
index 0000000..57b759f
--- /dev/null
+++ b/src/test/java/com/yourorg/StringIsEmptyTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.Recipe;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+@Disabled("Remove this annotation to run the tests once you implement the recipe")
+class StringIsEmptyTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name
+ // You might need to trigger an explicit build of your project to generate this class with Ctrl + F9
+
+ // TODO: Uncomment the line below once you have implemented the recipe
+ //spec.recipe(new StringIsEmptyRecipe());
+ }
+
+ @DocumentExample
+ @Test
+ void standardizeStringIsEmpty() {
+ // Notice how we pass in both the "before" and "after" code snippets
+ // This indicates that we expect the recipe to transform the "before" code snippet into the "after" code snippet
+ // If the recipe does not do this, the test will fail, and a diff will be shown
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class A {
+ void test(String s, boolean b) {
+ b = s.length() == 0;
+ b = 0 == s.length();
+ b = s.length() < 1;
+ b = 1 > s.length();
+ b = s.equals("");
+ b = "".equals(s);
+ b = s.isEmpty();
+ }
+ }
+ """,
+ """
+ class A {
+ void test(String s, boolean b) {
+ b = s.isEmpty();
+ b = s.isEmpty();
+ b = s.isEmpty();
+ b = s.isEmpty();
+ b = s.isEmpty();
+ b = s.isEmpty();
+ b = s.isEmpty();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void showStringTypeMatchAndSimplification() {
+ // Notice how the recipe will match anything that is of type String, not just local variables
+ // Take a closer look at the last two replacements to `true` and `false`.
+ // Open up the generated recipe and see if you can work out why those are replaced with booleans!
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class A {
+ String field;
+
+ String methodCall() {
+ return "Hello World";
+ }
+
+ void test(String argument) {
+ boolean bool1 = field.length() == 0;
+ boolean bool2 = methodCall().length() == 0;
+ boolean bool3 = argument.length() == 0;
+ boolean bool4 = "".length() == 0;
+ boolean bool5 = "literal".length() == 0;
+ }
+ }
+ """,
+ """
+ class A {
+ String field;
+
+ String methodCall() {
+ return "Hello World";
+ }
+
+ void test(String argument) {
+ boolean bool1 = field.isEmpty();
+ boolean bool2 = methodCall().isEmpty();
+ boolean bool3 = argument.isEmpty();
+ boolean bool4 = true;
+ boolean bool5 = false;
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doNothingForStringIsEmpty() {
+ // Notice how we only pass in the "before" code snippet, and not the "after" code snippet
+ // That indicates that we expect the recipe to do nothing in this case, and will fail if it does anything
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class A {
+ void test(String s, boolean b) {
+ b = s.isEmpty();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doNothingForCharSequence() {
+ // When a different type is used, the recipe should do nothing
+ // See if you can modify the recipe to handle CharSequence as well, or create a separate recipe for it
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class A {
+ void test(CharSequence s, boolean b) {
+ b = s.length() == 0;
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void recipeDocumentation() {
+ // This is a test to validate the correctness of the documentation in the recipe
+ // By default you get generated documentation, but you can customize it through the RecipeDescriptor annotation
+ Recipe recipe = null; // TODO: = new StringIsEmptyRecipe();
+ String displayName = recipe.getDisplayName();
+ String description = recipe.getDescription();
+ assert "Standardize empty String checks".equals(displayName) : displayName;
+ assert "Replace calls to `String.length() == 0` with `String.isEmpty()`.".equals(description) : description;
+ }
+}
diff --git a/src/test/java/com/yourorg/UpdateConcoursePipelineTest.java b/src/test/java/com/yourorg/UpdateConcoursePipelineTest.java
new file mode 100644
index 0000000..ff20087
--- /dev/null
+++ b/src/test/java/com/yourorg/UpdateConcoursePipelineTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RewriteTest;
+
+import java.nio.file.Paths;
+
+import static org.openrewrite.yaml.Assertions.yaml;
+
+class UpdateConcoursePipelineTest implements RewriteTest {
+
+ @DocumentExample
+ @Test
+ void updateTagFilter() {
+ rewriteRun(
+ spec -> spec.recipe(new UpdateConcoursePipeline("8.2.0")),
+ //language=yaml
+ yaml(
+ """
+ ---
+ resources:
+ - name: tasks
+ type: git
+ source:
+ uri: git@github.com:Example/concourse-tasks.git
+ tag_filter: 8.1.0
+ """,
+ """
+ ---
+ resources:
+ - name: tasks
+ type: git
+ source:
+ uri: git@github.com:Example/concourse-tasks.git
+ tag_filter: 8.2.0
+ """,
+ spec -> spec.path(Paths.get("ci/pipeline.yml"))
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/UseApacheStringUtilsTest.java b/src/test/java/com/yourorg/UseApacheStringUtilsTest.java
new file mode 100644
index 0000000..1a34678
--- /dev/null
+++ b/src/test/java/com/yourorg/UseApacheStringUtilsTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+class UseApacheStringUtilsTest implements RewriteTest {
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipeFromResources("com.yourorg.UseApacheStringUtils")
+ .parser(JavaParser.fromJavaVersion().classpath("commons-lang3", "spring-core"));
+ }
+
+ @DocumentExample
+ @Test
+ void replacesStringEquals() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import org.springframework.util.StringUtils;
+
+ class A {
+ boolean test(String s) {
+ return StringUtils.containsWhitespace(s);
+ }
+ }
+ """,
+ """
+ import org.apache.commons.lang3.StringUtils;
+
+ class A {
+ boolean test(String s) {
+ return StringUtils.containsWhitespace(s);
+ }
+ }
+ """
+ )
+ );
+ }
+}
diff --git a/src/test/java/com/yourorg/UseOpenRewriteNullableTest.java b/src/test/java/com/yourorg/UseOpenRewriteNullableTest.java
new file mode 100644
index 0000000..e1ca70d
--- /dev/null
+++ b/src/test/java/com/yourorg/UseOpenRewriteNullableTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.yourorg;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+// This is a test for the UseOpenRewriteNullable recipe, as an example of how to write a test for a declarative recipe.
+class UseOpenRewriteNullableTest implements RewriteTest {
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec
+ // Use the fully qualified class name of the recipe defined in src/main/resources/META-INF/rewrite/rewrite.yml
+ .recipeFromResources("com.yourorg.UseOpenRewriteNullable")
+ // The before and after text blocks contain references to annotations from these two classpath entries
+ .parser(JavaParser.fromJavaVersion().classpath("annotations", "rewrite-core"));
+ }
+
+ @DocumentExample
+ @Test
+ void replacesNullableAnnotation() {
+ rewriteRun(
+ // Composite recipes are a hierarchy of recipes that can be applied in a single pass.
+ // To view what the composite recipe does, you can use the RecipePrinter to print the recipe to the console.
+ spec -> spec.printRecipe(() -> System.out::println),
+ //language=java
+ java(
+ """
+ import org.jetbrains.annotations.Nullable;
+
+ class A {
+ @Nullable
+ String s;
+ }
+ """,
+ """
+ import org.openrewrite.internal.lang.Nullable;
+
+ class A {
+ @Nullable
+ String s;
+ }
+ """
+ )
+ );
+ }
+}