diff --git a/rewrite-core/src/main/java/org/openrewrite/PackageRecipeTags.java b/rewrite-core/src/main/java/org/openrewrite/PackageRecipeTags.java new file mode 100644 index 00000000000..aefcaef91ec --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/PackageRecipeTags.java @@ -0,0 +1,39 @@ +/* + * 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 org.openrewrite; + +import org.openrewrite.config.RecipeDescriptor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A package-level annotation that can be used to tag all {@link Recipe} within a given package and children + * packages with a given tag. + *

+ * This annotation is useful for tagging all recipes within a package with a given tag, without having to + * add it manually to each overridden {@link Recipe#getTags()} method. + *

+ * The tag will not be accessible via {@link Recipe#getTags()}, but will be accessible via {@link RecipeDescriptor#getTags()} + * when the {@link RecipeDescriptor} is created via {@link Recipe#getDescriptor()}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PACKAGE}) +public @interface PackageRecipeTags { + String[] value() default {}; +} diff --git a/rewrite-core/src/main/java/org/openrewrite/Recipe.java b/rewrite-core/src/main/java/org/openrewrite/Recipe.java index fc7603d8c98..1d63ec89a26 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Recipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/Recipe.java @@ -223,10 +223,45 @@ protected RecipeDescriptor createRecipeDescriptor() { } catch (URISyntaxException e) { throw new RuntimeException(e); } + Set allTags = new LinkedHashSet<>(getTags()); + allTags.addAll(getTagsForPackageAndParent(getClass().getPackage())); + + return new RecipeDescriptor( + getName(), + getDisplayName(), + getDescription(), + allTags, + getEstimatedEffortPerOccurrence(), + options, + recipeList1, + getDataTableDescriptors(), + getMaintainers(), + getContributors(), + getExamples(), + recipeSource + ); + } - return new RecipeDescriptor(getName(), getDisplayName(), getDescription(), getTags(), - getEstimatedEffortPerOccurrence(), options, recipeList1, getDataTableDescriptors(), - getMaintainers(), getContributors(), getExamples(), recipeSource); + /** + * Finds all {@link PackageRecipeTags} annotations on the package of the given class and all parent packages. + * + * @return The set of tags found. + */ + private static Set getTagsForPackageAndParent(@Nullable Package p) { + if (p == null) { + return Collections.emptySet(); + } + Set tags = new LinkedHashSet<>(); + PackageRecipeTags packageTags = p.getAnnotation(PackageRecipeTags.class); + if (packageTags != null) { + tags.addAll(Arrays.asList(packageTags.value())); + } + int lastDot = p.getName().lastIndexOf('.'); + if (lastDot > 0) { + String parentPackage = p.getName().substring(0, lastDot); + tags.addAll(getTagsForPackageAndParent(Package.getPackage(parentPackage))); + } + return tags; } private List getOptionDescriptors(Class recipeClass) { diff --git a/rewrite-core/src/test/java/org/openrewrite/tags/PackageRecipeTagsAnnotationTest.java b/rewrite-core/src/test/java/org/openrewrite/tags/PackageRecipeTagsAnnotationTest.java new file mode 100644 index 00000000000..4dc76696ce7 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/tags/PackageRecipeTagsAnnotationTest.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 org.openrewrite.tags; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Recipe; +import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.tags.child.ChildRecipeWithNoTags; + +import java.util.Collections; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PackageRecipeTagsAnnotationTest { + + private static class RecipeWithNoTags extends Recipe { + @Override + public String getDisplayName() { + return "Recipe with no tags"; + } + + @Override + public String getDescription() { + return "It's tag-less!"; + } + + @Override + public Set getTags() { + // Look ma, no tags! 🤣 + return Collections.emptySet(); + } + } + + @Test + void testRecipeWithNoTags() { + RecipeWithNoTags recipe = new RecipeWithNoTags(); + RecipeDescriptor descriptor = recipe.getDescriptor(); + assertThat(descriptor.getTags()) + .withFailMessage("Expected tag to be pulled from package-info.java") + .contains("What a fun tag!"); + } + + @Test + void testChildRecipeWithNoTags() { + ChildRecipeWithNoTags recipe = new ChildRecipeWithNoTags(); + RecipeDescriptor descriptor = recipe.getDescriptor(); + assertThat(descriptor.getTags()) + .withFailMessage("Expected tag to be pulled from parent package-info.java") + .contains("What a fun tag!"); + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/tags/child/ChildRecipeWithNoTags.java b/rewrite-core/src/test/java/org/openrewrite/tags/child/ChildRecipeWithNoTags.java new file mode 100644 index 00000000000..403118450bb --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/tags/child/ChildRecipeWithNoTags.java @@ -0,0 +1,32 @@ +/* + * 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 org.openrewrite.tags.child; + +import org.openrewrite.Recipe; + +public class ChildRecipeWithNoTags extends Recipe { + + @Override + public String getDisplayName() { + return "Recipe with no tags"; + } + + @Override + public String getDescription() { + return "It's tag-less!"; + } + +} diff --git a/rewrite-core/src/test/java/org/openrewrite/tags/child/package-info.java b/rewrite-core/src/test/java/org/openrewrite/tags/child/package-info.java new file mode 100644 index 00000000000..587c6d5a95d --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/tags/child/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NonNullApi +package org.openrewrite.tags.child; + +import io.micrometer.core.lang.NonNullApi; diff --git a/rewrite-core/src/test/java/org/openrewrite/tags/package-info.java b/rewrite-core/src/test/java/org/openrewrite/tags/package-info.java new file mode 100644 index 00000000000..a7f6d95cee9 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/tags/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ +@PackageRecipeTags({"What a fun tag!"}) +@NonNullApi +package org.openrewrite.tags; + +import org.openrewrite.PackageRecipeTags; +import org.openrewrite.internal.lang.NonNullApi;