diff --git a/README.md b/README.md index 46ce97e..1d48bec 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ ![Logo](https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss.png) -### Migrate LaunchDarkly. Automatically. +### Migrate feature flags. Automatically. -[![ci](https://github.com/openrewrite/rewrite-launchdarkly/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite-launchdarkly/actions/workflows/ci.yml) -[![Apache 2.0](https://img.shields.io/github/license/openrewrite/rewrite-launchdarkly.svg)](https://www.apache.org/licenses/LICENSE-2.0) -[![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite.recipe/rewrite-launchdarkly.svg)](https://mvnrepository.com/artifact/org.openrewrite.recipe/rewrite-launchdarkly) +[![ci](https://github.com/openrewrite/rewrite-feature-flags/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite-feature-flags/actions/workflows/ci.yml) +[![Apache 2.0](https://img.shields.io/github/license/openrewrite/rewrite-feature-flags.svg)](https://www.apache.org/licenses/LICENSE-2.0) +[![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite.recipe/rewrite-feature-flags.svg)](https://mvnrepository.com/artifact/org.openrewrite.recipe/rewrite-feature-flags) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) ### What is this? -This project implements a [Rewrite module](https://github.com/openrewrite/rewrite) that performs common tasks when migrating to new version of LaunchDarkly. +This project implements a [Rewrite module](https://github.com/openrewrite/rewrite) that performs common tasks related to feature flags, or migrating to new versions of LaunchDarkly. -Browse [a selection of recipes available through this module in the recipe catalog](https://docs.openrewrite.org/recipes/launchdarkly). +Browse [a selection of recipes available through this module in the recipe catalog](https://docs.openrewrite.org/recipes/featureflags). ## Contributing diff --git a/build.gradle.kts b/build.gradle.kts index 8829e3f..c1c4ca7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } group = "org.openrewrite.recipe" -description = "LaunchDarkly Migration" +description = "Feature flag migration" val rewriteVersion = rewriteRecipe.rewriteVersion.get() dependencies { @@ -21,6 +21,10 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:latest.release") + testImplementation("dev.openfeature:sdk:latest.release") + testImplementation("io.getunleash:unleash-client-java:latest.release") + testImplementation("org.ff4j:ff4j-core:2.0.0") // 2.1.x requires Java 21 + testRuntimeOnly("org.gradle:gradle-tooling-api:latest.release") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 84afeb0..1251206 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "rewrite-launchdarkly" +rootProject.name = "rewrite-feature-flags" plugins { id("com.gradle.develocity") version "latest.release" diff --git a/src/main/java/org/openrewrite/launchdarkly/RemoveBoolVariation.java b/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java similarity index 76% rename from src/main/java/org/openrewrite/launchdarkly/RemoveBoolVariation.java rename to src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java index b78ad16..cc53893 100644 --- a/src/main/java/org/openrewrite/launchdarkly/RemoveBoolVariation.java +++ b/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags; import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; import org.openrewrite.analysis.constantfold.ConstantFold; import org.openrewrite.analysis.util.CursorUtil; -import org.openrewrite.internal.StringUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.UsesMethod; @@ -34,24 +32,25 @@ import org.openrewrite.staticanalysis.RemoveUnusedPrivateFields; import org.openrewrite.staticanalysis.SimplifyConstantIfBranchExecution; -import java.util.Optional; - @Value @EqualsAndHashCode(callSuper = false) -public class RemoveBoolVariation extends Recipe { - - private static final String METHOD_PATTERN_BOOLVARIATION = "com.launchdarkly.sdk.server.LDClient boolVariation(String, com.launchdarkly.sdk.*, boolean)"; +public class RemoveBooleanFlag extends Recipe { @Override public String getDisplayName() { - return "Remove `boolVariation` for feature key"; + return "Remove a boolean feature flag for feature key"; } @Override public String getDescription() { - return "Replace `boolVariation` invocations for feature key with value, and simplify constant if branch execution."; + return "Replace method invocations for feature key with value, and simplify constant if branch execution."; } + @Option(displayName = "Method pattern", + description = "A method pattern to match against. The first argument must be the feature key as `String`.", + example = "dev.openfeature.sdk.Client getBooleanValue(String, Boolean)") + String methodPattern; + @Option(displayName = "Feature flag key", description = "The key of the feature flag to remove.", example = "flag-key-123abc") @@ -62,18 +61,9 @@ public String getDescription() { example = "true") Boolean replacementValue; - @Option(displayName = "Method pattern", - description = "A method pattern to match against. If not specified, will match `LDClient` `boolVariation`. " + - "The first argument must be the feature key as `String`.", - example = METHOD_PATTERN_BOOLVARIATION, - required = false) - @Nullable - String methodPattern; - @Override public TreeVisitor getVisitor() { - String pattern = Optional.ofNullable(methodPattern).filter(StringUtils::isNotEmpty).orElse(METHOD_PATTERN_BOOLVARIATION); - final MethodMatcher methodMatcher = new MethodMatcher(pattern, true); + final MethodMatcher methodMatcher = new MethodMatcher(methodPattern, true); JavaVisitor visitor = new JavaVisitor() { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { diff --git a/src/main/java/org/openrewrite/featureflags/ff4j/RemoveCheck.java b/src/main/java/org/openrewrite/featureflags/ff4j/RemoveCheck.java new file mode 100644 index 0000000..f5c77c8 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/ff4j/RemoveCheck.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 org.openrewrite.featureflags.ff4j; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.featureflags.RemoveBooleanFlag; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveCheck extends Recipe { + + @Option(displayName = "Feature flag key", + description = "The key of the feature flag to remove.", + example = "flag-key-123abc") + String featureKey; + + @Option(displayName = "Replacement value", + description = "The value to replace the feature flag check with.", + example = "true") + Boolean replacementValue; + + @Override + public String getDisplayName() { + return "Remove FF4j's `check` for feature key"; + } + + @Override + public String getDescription() { + return "Replace `check()` invocations for `featureKey` with `replacementValue`, and simplify constant if branch execution."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new RemoveBooleanFlag( + "org.ff4j.FF4j check(String)", + featureKey, replacementValue)); + } +} diff --git a/src/main/java/org/openrewrite/launchdarkly/search/package-info.java b/src/main/java/org/openrewrite/featureflags/ff4j/package-info.java similarity index 94% rename from src/main/java/org/openrewrite/launchdarkly/search/package-info.java rename to src/main/java/org/openrewrite/featureflags/ff4j/package-info.java index 14d74ed..44a39d9 100644 --- a/src/main/java/org/openrewrite/launchdarkly/search/package-info.java +++ b/src/main/java/org/openrewrite/featureflags/ff4j/package-info.java @@ -15,7 +15,7 @@ */ @NonNullApi @NonNullFields -package org.openrewrite.launchdarkly.search; +package org.openrewrite.featureflags.ff4j; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/ff4j/search/FindFeatureFlag.java b/src/main/java/org/openrewrite/featureflags/ff4j/search/FindFeatureFlag.java new file mode 100644 index 0000000..65929fe --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/ff4j/search/FindFeatureFlag.java @@ -0,0 +1,53 @@ +/* + * 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.featureflags.ff4j.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindFeatureFlag extends Recipe { + + @Option(displayName = "Feature key", + description = "The unique key for the feature flag.", + example = "flag-key-123abc", + required = false) + @Nullable + String featureKey; + + @Override + public String getDisplayName() { + return "Find a FF4j feature flag"; + } + + @Override + public String getDescription() { + return "Find a FF4j feature flag."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new org.openrewrite.featureflags.search.FindFeatureFlag( + "org.ff4j.FF4j check(String, ..)", featureKey)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/ff4j/search/package-info.java b/src/main/java/org/openrewrite/featureflags/ff4j/search/package-info.java new file mode 100644 index 0000000..e11c0ca --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/ff4j/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.ff4j.search; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/launchdarkly/ChangeVariationDefault.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefault.java similarity index 99% rename from src/main/java/org/openrewrite/launchdarkly/ChangeVariationDefault.java rename to src/main/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefault.java index f8ae98d..1a25ed1 100644 --- a/src/main/java/org/openrewrite/launchdarkly/ChangeVariationDefault.java +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefault.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/src/main/java/org/openrewrite/launchdarkly/MigrateUserToContext.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContext.java similarity index 99% rename from src/main/java/org/openrewrite/launchdarkly/MigrateUserToContext.java rename to src/main/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContext.java index 2fcd38c..321ac95 100644 --- a/src/main/java/org/openrewrite/launchdarkly/MigrateUserToContext.java +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariation.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariation.java new file mode 100644 index 0000000..32976f7 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariation.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 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.featureflags.launchdarkly; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.featureflags.RemoveBooleanFlag; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveBoolVariation extends Recipe { + + @Override + public String getDisplayName() { + return "Remove LaunchDarkly's `boolVariation` for feature key"; + } + + @Override + public String getDescription() { + return "Replace `boolVariation` invocations for feature key with value, and simplify constant if branch execution."; + } + + @Option(displayName = "Feature flag key", + description = "The key of the feature flag to remove.", + example = "flag-key-123abc") + String featureKey; + + @Option(displayName = "Replacement value", + description = "The value to replace the feature flag check with.", + example = "true") + Boolean replacementValue; + + @Override + public List getRecipeList() { + return Collections.singletonList(new RemoveBooleanFlag( + "com.launchdarkly.sdk.server.LDClient boolVariation(String, com.launchdarkly.sdk.*, boolean)", + featureKey, replacementValue)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/launchdarkly/package-info.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/package-info.java new file mode 100644 index 0000000..94b620d --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.launchdarkly; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/launchdarkly/search/FindFeatureFlag.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlag.java similarity index 83% rename from src/main/java/org/openrewrite/launchdarkly/search/FindFeatureFlag.java rename to src/main/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlag.java index 1947901..f0ef0c9 100644 --- a/src/main/java/org/openrewrite/launchdarkly/search/FindFeatureFlag.java +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlag.java @@ -13,24 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly.search; +package org.openrewrite.featureflags.launchdarkly.search; import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.analysis.InvocationMatcher; import org.openrewrite.analysis.constantfold.ConstantFold; import org.openrewrite.analysis.dataflow.DataFlowNode; import org.openrewrite.analysis.dataflow.DataFlowSpec; import org.openrewrite.analysis.dataflow.Dataflow; -import org.openrewrite.analysis.trait.expr.Literal; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.marker.SearchResult; @@ -38,7 +35,7 @@ @Value @EqualsAndHashCode(callSuper = false) public class FindFeatureFlag extends Recipe { - @Option(displayName = "Flag Type", + @Option(displayName = "Flag type", description = "The feature flag's type.", example = "Bool", valid = {"Bool", "Double", "Int", "JsonValue", "Migration", "String"}, @@ -46,7 +43,7 @@ public class FindFeatureFlag extends Recipe { @Nullable FeatureFlagType flagType; - @Option(displayName = "Feature Key", + @Option(displayName = "Feature key", description = "The unique key for the feature flag.", example = "flag-key-123abc", required = false) @@ -65,12 +62,12 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - MethodMatcher launchDarklyClientMatcher = new MethodMatcher("com.launchdarkly.sdk.server.LDClient *Variation(..)"); - return new JavaIsoVisitor() { + MethodMatcher methodMatcher = new MethodMatcher("com.launchdarkly.sdk.server.LDClient *Variation(..)"); + return Preconditions.check(new UsesMethod<>(methodMatcher), new JavaIsoVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - if (!launchDarklyClientMatcher.matches(m)) { + if (!methodMatcher.matches(m)) { return m; } @@ -100,12 +97,19 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu @Override public Expression visitExpression(Expression expression, ExecutionContext ctx) { Expression e = super.visitExpression(expression, ctx); + if (findFeatureKeyFlow() && methodMatcher.matches(getCursor().firstEnclosing(J.MethodInvocation.class))) { + getCursor().putMessageOnFirstEnclosing(J.MethodInvocation.class, "feature.found", true); + } + return e; + } + + private boolean findFeatureKeyFlow() { if (StringUtils.isBlank(featureKey)) { - return e; + return false; } - InvocationMatcher matcher = InvocationMatcher.fromMethodMatcher(launchDarklyClientMatcher); - boolean found = Dataflow.startingAt(getCursor()) + InvocationMatcher matcher = InvocationMatcher.fromMethodMatcher(methodMatcher); + return Dataflow.startingAt(getCursor()) .findSinks(new DataFlowSpec() { @Override public boolean isSource(DataFlowNode srcNode) { @@ -120,15 +124,8 @@ public boolean isSink(DataFlowNode sinkNode) { return matcher.advanced().isFirstParameter(sinkNode.getCursor()); } }).isSome(); - if (found) { - J.MethodInvocation m = getCursor().firstEnclosing(J.MethodInvocation.class); - if (launchDarklyClientMatcher.matches(m)) { - getCursor().putMessageOnFirstEnclosing(J.MethodInvocation.class, "feature.found", true); - } - } - return e; } - }; + }); } public enum FeatureFlagType { diff --git a/src/main/java/org/openrewrite/featureflags/launchdarkly/search/package-info.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/search/package-info.java new file mode 100644 index 0000000..08c2a07 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.launchdarkly.search; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValue.java b/src/main/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValue.java new file mode 100644 index 0000000..ad080bd --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValue.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 org.openrewrite.featureflags.openfeature; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.featureflags.RemoveBooleanFlag; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveGetBooleanValue extends Recipe { + + @Option(displayName = "Feature flag key", + description = "The key of the feature flag to remove.", + example = "flag-key-123abc") + String featureKey; + + @Option(displayName = "Replacement value", + description = "The value to replace the feature flag check with.", + example = "true") + Boolean replacementValue; + + @Override + public String getDisplayName() { + return "Remove OpenFeature's `getBooleanValue` for feature key"; + } + + @Override + public String getDescription() { + return "Replace `getBooleanValue()` invocations for `featureKey` with `replacementValue`, and simplify constant if branch execution."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new RemoveBooleanFlag( + "dev.openfeature.sdk.Features getBooleanValue(String, Boolean)", + featureKey, replacementValue)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/openfeature/package-info.java b/src/main/java/org/openrewrite/featureflags/openfeature/package-info.java new file mode 100644 index 0000000..9095013 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/openfeature/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.openfeature; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/openfeature/search/FindFeatureFlag.java b/src/main/java/org/openrewrite/featureflags/openfeature/search/FindFeatureFlag.java new file mode 100644 index 0000000..2b20714 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/openfeature/search/FindFeatureFlag.java @@ -0,0 +1,53 @@ +/* + * 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.featureflags.openfeature.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindFeatureFlag extends Recipe { + + @Option(displayName = "Feature key", + description = "The unique key for the feature flag.", + example = "flag-key-123abc", + required = false) + @Nullable + String featureKey; + + @Override + public String getDisplayName() { + return "Find an OpenFeature feature flag"; + } + + @Override + public String getDescription() { + return "Find an OpenFeature feature flag."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new org.openrewrite.featureflags.search.FindFeatureFlag( + "dev.openfeature.sdk.Features get*Value(String, ..)", featureKey)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/openfeature/search/package-info.java b/src/main/java/org/openrewrite/featureflags/openfeature/search/package-info.java new file mode 100644 index 0000000..889fd7c --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/openfeature/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.openfeature.search; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/launchdarkly/package-info.java b/src/main/java/org/openrewrite/featureflags/package-info.java similarity index 95% rename from src/main/java/org/openrewrite/launchdarkly/package-info.java rename to src/main/java/org/openrewrite/featureflags/package-info.java index 46d4008..6ad96c3 100644 --- a/src/main/java/org/openrewrite/launchdarkly/package-info.java +++ b/src/main/java/org/openrewrite/featureflags/package-info.java @@ -15,7 +15,7 @@ */ @NonNullApi @NonNullFields -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/search/FindFeatureFlag.java b/src/main/java/org/openrewrite/featureflags/search/FindFeatureFlag.java new file mode 100644 index 0000000..91e7dce --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/search/FindFeatureFlag.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 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.featureflags.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.analysis.InvocationMatcher; +import org.openrewrite.analysis.constantfold.ConstantFold; +import org.openrewrite.analysis.dataflow.DataFlowNode; +import org.openrewrite.analysis.dataflow.DataFlowSpec; +import org.openrewrite.analysis.dataflow.Dataflow; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindFeatureFlag extends Recipe { + + @Option(displayName = "Method pattern", + description = "A method pattern to match against. The first argument must be the feature key as `String`.", + example = "dev.openfeature.sdk.Client getBooleanValue(String, Boolean)") + String methodPattern; + + @Option(displayName = "Feature key", + description = "The unique key for the feature flag.", + example = "flag-key-123abc", + required = false) + @Nullable + String featureKey; + + @Override + public String getDisplayName() { + return "Find a feature flag"; + } + + @Override + public String getDescription() { + return "Find a feature flag matching method pattern."; + } + + @Override + public TreeVisitor getVisitor() { + MethodMatcher methodMatcher = new MethodMatcher(methodPattern, true); + return Preconditions.check(new UsesMethod<>(methodMatcher), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + if (!methodMatcher.matches(m)) { + return m; + } + + if (StringUtils.isBlank(featureKey)) { + return SearchResult.found(m); + } + + Boolean matchesFeatureKey = getCursor().getMessage("feature.found"); + if (matchesFeatureKey != null && matchesFeatureKey) { + return SearchResult.found(m); + } + + return m; + } + + @Override + public Expression visitExpression(Expression expression, ExecutionContext ctx) { + Expression e = super.visitExpression(expression, ctx); + if (findFeatureKeyFlow() && methodMatcher.matches(getCursor().firstEnclosing(J.MethodInvocation.class))) { + getCursor().putMessageOnFirstEnclosing(J.MethodInvocation.class, "feature.found", true); + } + return e; + } + + private boolean findFeatureKeyFlow() { + if (StringUtils.isBlank(featureKey)){ + return false; + } + + InvocationMatcher matcher = InvocationMatcher.fromMethodMatcher(methodMatcher); + return Dataflow.startingAt(getCursor()) + .findSinks(new DataFlowSpec() { + @Override + public boolean isSource(DataFlowNode srcNode) { + return ConstantFold.findConstantLiteralValue(srcNode, String.class) + .map(featureKey::equals) + .orSome(false); + } + + @Override + public boolean isSink(DataFlowNode sinkNode) { + return matcher.advanced().isFirstParameter(sinkNode.getCursor()); + } + }).isSome(); + } + }); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/search/package-info.java b/src/main/java/org/openrewrite/featureflags/search/package-info.java new file mode 100644 index 0000000..45ac455 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.search; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/unleash/RemoveIsEnabled.java b/src/main/java/org/openrewrite/featureflags/unleash/RemoveIsEnabled.java new file mode 100644 index 0000000..560f768 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/unleash/RemoveIsEnabled.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 org.openrewrite.featureflags.unleash; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.featureflags.RemoveBooleanFlag; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveIsEnabled extends Recipe { + + @Option(displayName = "Feature flag key", + description = "The key of the feature flag to remove.", + example = "flag-key-123abc") + String featureKey; + + @Option(displayName = "Replacement value", + description = "The value to replace the feature flag check with.", + example = "true") + Boolean replacementValue; + + @Override + public String getDisplayName() { + return "Remove Unleash's `isEnabled` for feature key"; + } + + @Override + public String getDescription() { + return "Replace `isEnabled()` invocations for `featureKey` with `replacementValue`, and simplify constant if branch execution."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new RemoveBooleanFlag( + "io.getunleash.Unleash isEnabled(String, ..)", + featureKey, replacementValue)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/unleash/package-info.java b/src/main/java/org/openrewrite/featureflags/unleash/package-info.java new file mode 100644 index 0000000..4e6fb09 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/unleash/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.unleash; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/featureflags/unleash/search/FindFeatureFlag.java b/src/main/java/org/openrewrite/featureflags/unleash/search/FindFeatureFlag.java new file mode 100644 index 0000000..d58b3bb --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/unleash/search/FindFeatureFlag.java @@ -0,0 +1,53 @@ +/* + * 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.featureflags.unleash.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindFeatureFlag extends Recipe { + + @Option(displayName = "Feature key", + description = "The unique key for the feature flag.", + example = "flag-key-123abc", + required = false) + @Nullable + String featureKey; + + @Override + public String getDisplayName() { + return "Find an Unleash feature flag"; + } + + @Override + public String getDescription() { + return "Find an Unleash feature flag."; + } + + @Override + public List getRecipeList() { + return Collections.singletonList(new org.openrewrite.featureflags.search.FindFeatureFlag( + "io.getunleash.Unleash isEnabled(String, ..)", featureKey)); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/unleash/search/package-info.java b/src/main/java/org/openrewrite/featureflags/unleash/search/package-info.java new file mode 100644 index 0000000..8e1ac2f --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/unleash/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 +@NonNullFields +package org.openrewrite.featureflags.unleash.search; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/resources/META-INF/rewrite/category.yml b/src/main/resources/META-INF/rewrite/category.yml index 892dac7..d2d9a56 100644 --- a/src/main/resources/META-INF/rewrite/category.yml +++ b/src/main/resources/META-INF/rewrite/category.yml @@ -14,8 +14,28 @@ # limitations under the License. # +--- +type: specs.openrewrite.org/v1beta/category +name: Feature flags +packageName: org.openrewrite.featureflags +description: Recipes to perform feature flag migration tasks. --- type: specs.openrewrite.org/v1beta/category name: LaunchDarkly -packageName: org.openrewrite.launchdarkly +packageName: org.openrewrite.featureflags.launchdarkly description: Recipes to perform [LaunchDarkly](https://launchdarkly.com/) migration tasks. +--- +type: specs.openrewrite.org/v1beta/category +name: OpenFeature +packageName: org.openrewrite.featureflags.ff4j +description: Recipes to perform [FF4j](https://ff4j.org/) migration tasks. +--- +type: specs.openrewrite.org/v1beta/category +name: OpenFeature +packageName: org.openrewrite.featureflags.openfeature +description: Recipes to perform [OpenFeature](https://openfeature.dev/) migration tasks. +--- +type: specs.openrewrite.org/v1beta/category +name: OpenFeature +packageName: org.openrewrite.featureflags.unleash +description: Recipes to perform [Unleash](https://getunleash.io/) migration tasks. diff --git a/src/main/resources/META-INF/rewrite/launchdarkly-6.yml b/src/main/resources/META-INF/rewrite/launchdarkly-6.yml index 2068be9..acfe5c8 100644 --- a/src/main/resources/META-INF/rewrite/launchdarkly-6.yml +++ b/src/main/resources/META-INF/rewrite/launchdarkly-6.yml @@ -16,7 +16,7 @@ --- type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.launchdarkly.UpgradeLaunchDarkly6 +name: org.openrewrite.featureflags.launchdarkly.UpgradeLaunchDarkly6 displayName: Migrate to LaunchDarkly 6.x description: This recipe will apply changes commonly needed when migrating to LaunchDarkly 6.x. recipeList: @@ -25,4 +25,4 @@ recipeList: groupId: com.launchdarkly artifactId: launchdarkly-java-server-sdk newVersion: 6.x - - org.openrewrite.launchdarkly.MigrateUserToContext + - org.openrewrite.featureflags.launchdarkly.MigrateUserToContext diff --git a/src/main/resources/META-INF/rewrite/launchdarkly-7.yml b/src/main/resources/META-INF/rewrite/launchdarkly-7.yml index 21686cd..2559789 100644 --- a/src/main/resources/META-INF/rewrite/launchdarkly-7.yml +++ b/src/main/resources/META-INF/rewrite/launchdarkly-7.yml @@ -16,11 +16,11 @@ --- type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.launchdarkly.UpgradeLaunchDarkly7 +name: org.openrewrite.featureflags.launchdarkly.UpgradeLaunchDarkly7 displayName: Migrate to LaunchDarkly 7.x description: This recipe will apply changes commonly needed when migrating to LaunchDarkly 7.x. recipeList: - - org.openrewrite.launchdarkly.UpgradeLaunchDarkly6 + - org.openrewrite.featureflags.launchdarkly.UpgradeLaunchDarkly6 # https://docs.launchdarkly.com/sdk/server-side/java/migration-6-to-7 - org.openrewrite.java.dependencies.UpgradeDependencyVersion: groupId: com.launchdarkly diff --git a/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java b/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java new file mode 100644 index 0000000..117f613 --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java @@ -0,0 +1,125 @@ +/* + * 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.featureflags; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; + +import static org.openrewrite.java.Assertions.java; + +class RemoveBooleanFlagTest implements RewriteTest { + + @Test + void customMethodPatternForWrapper() { + rewriteRun( + spec -> spec.recipe(new RemoveBooleanFlag("com.acme.bank.CustomLaunchDarklyWrapper featureFlagEnabled(String, boolean)", "flag-key-123abc", true)), + // language=java + java( + """ + package com.acme.bank; + + public class CustomLaunchDarklyWrapper { + public boolean featureFlagEnabled(String key, boolean fallback) { + return fallback; + } + } + """ + ), + // language=java + java( + """ + import com.acme.bank.CustomLaunchDarklyWrapper; + class Foo { + private CustomLaunchDarklyWrapper wrapper = new CustomLaunchDarklyWrapper(); + void bar() { + if (wrapper.featureFlagEnabled("flag-key-123abc", false)) { + // Application code to show the feature + System.out.println("Feature is on"); + } + else { + // The code to run if the feature is off + System.out.println("Feature is off"); + } + } + } + """, + """ + class Foo { + void bar() { + // Application code to show the feature + System.out.println("Feature is on"); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-feature-flags/issues/23") + void customMethodPatternNoConstants() { + // language=java + rewriteRun( + spec -> spec.recipe(new RemoveBooleanFlag("com.osd.util.ToggleChecker isToggleEnabled(String, boolean)", "FEATURE_TOGGLE1", true)), + java( + """ + package com.osd.util; + import java.util.Map; + import java.util.HashMap; + + public class ToggleChecker { + public boolean isToggleEnabled(String toggleName, boolean fallback) { + Map toggleMap = new HashMap<>(); + toggleMap.put("FEATURE_TOGGLE1", true); + toggleMap.put("FEATURE_TOGGLE2", true); + toggleMap.put("FEATURE_TOGGLE3", false); + return toggleMap.containsKey(toggleName); + } + } + """, + SourceSpec::skip + ), + java( + """ + import com.osd.util.ToggleChecker; + class Foo { + private ToggleChecker checker = new ToggleChecker(); + void bar() { + if (checker.isToggleEnabled("FEATURE_TOGGLE1", false)) { + // Application code to show the feature + System.out.println("Feature is on"); + } + else { + // The code to run if the feature is off + System.out.println("Feature is off"); + } + } + } + """, + """ + class Foo { + void bar() { + // Application code to show the feature + System.out.println("Feature is on"); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/featureflags/ff4j/RemoveCheckTest.java b/src/test/java/org/openrewrite/featureflags/ff4j/RemoveCheckTest.java new file mode 100644 index 0000000..de3e8a7 --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/ff4j/RemoveCheckTest.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.featureflags.ff4j; + +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 RemoveCheckTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveCheck("flag-key-123abc", true)) + .parser(JavaParser.fromJavaVersion().classpath("ff4j-core")); + } + + @Test + @DocumentExample + void removeCheck() { + rewriteRun( + //language=java + java( + """ + import org.ff4j.FF4j; + + class Test { + void bar(FF4j ff4j) { + if (ff4j.check("flag-key-123abc")) { + System.out.println("Feature enabled"); + } else { + System.out.println("Feature disabled"); + } + } + } + """, + """ + import org.ff4j.FF4j; + + class Test { + void bar(FF4j ff4j) { + System.out.println("Feature enabled"); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/launchdarkly/ChangeVariationDefaultTest.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefaultTest.java similarity index 99% rename from src/test/java/org/openrewrite/launchdarkly/ChangeVariationDefaultTest.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefaultTest.java index 0a35fec..7191810 100644 --- a/src/test/java/org/openrewrite/launchdarkly/ChangeVariationDefaultTest.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/ChangeVariationDefaultTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/openrewrite/launchdarkly/MigrateUserToContextTest.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContextTest.java similarity index 98% rename from src/test/java/org/openrewrite/launchdarkly/MigrateUserToContextTest.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContextTest.java index ea8b23d..c0d40e8 100644 --- a/src/test/java/org/openrewrite/launchdarkly/MigrateUserToContextTest.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/MigrateUserToContextTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; diff --git a/src/test/java/org/openrewrite/launchdarkly/RemoveBoolVariationTest.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariationTest.java similarity index 71% rename from src/test/java/org/openrewrite/launchdarkly/RemoveBoolVariationTest.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariationTest.java index 2a59a16..b2bd753 100644 --- a/src/test/java/org/openrewrite/launchdarkly/RemoveBoolVariationTest.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveBoolVariationTest.java @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import org.openrewrite.test.SourceSpec; import static org.openrewrite.java.Assertions.java; @@ -30,7 +28,7 @@ class RemoveBoolVariationTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new RemoveBoolVariation("flag-key-123abc", true, null)) + spec.recipe(new RemoveBoolVariation("flag-key-123abc", true)) .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "launchdarkly-java-server-sdk-6")); } @@ -147,7 +145,7 @@ void bar() { @Test void disablePermanently() { rewriteRun( - spec -> spec.recipe(new RemoveBoolVariation("flag-key-123abc", false, null)), + spec -> spec.recipe(new RemoveBoolVariation("flag-key-123abc", false)), // language=java java( """ @@ -290,109 +288,4 @@ void bar() { ) ); } - - @Test - void customMethodPatternForWrapper() { - rewriteRun( - spec -> spec.recipe(new RemoveBoolVariation("flag-key-123abc", true, "com.acme.bank.CustomLaunchDarklyWrapper featureFlagEnabled(String, boolean)")), - // language=java - java( - """ - package com.acme.bank; - - import com.launchdarkly.sdk.LDContext; - import com.launchdarkly.sdk.server.LDClient; - - public class CustomLaunchDarklyWrapper { - private LDClient client = new LDClient("sdk-key-123abc"); - public boolean featureFlagEnabled(String key, boolean fallback) { - LDContext context = null; - return client.boolVariation(key, context, false); - } - } - """ - ), - // language=java - java( - """ - import com.acme.bank.CustomLaunchDarklyWrapper; - class Foo { - private CustomLaunchDarklyWrapper wrapper = new CustomLaunchDarklyWrapper(); - void bar() { - if (wrapper.featureFlagEnabled("flag-key-123abc", false)) { - // Application code to show the feature - System.out.println("Feature is on"); - } - else { - // The code to run if the feature is off - System.out.println("Feature is off"); - } - } - } - """, - """ - class Foo { - void bar() { - // Application code to show the feature - System.out.println("Feature is on"); - } - } - """ - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite-launchdarkly/issues/23") - void customMethodPatternNoConstants() { - RemoveBoolVariation featureToggle1 = new RemoveBoolVariation("FEATURE_TOGGLE1", true, "com.osd.util.ToggleChecker isToggleEnabled(String, boolean)"); - // language=java - rewriteRun( - spec -> spec.recipe(featureToggle1), - java( - """ - package com.osd.util; - import java.util.Map; - import java.util.HashMap; - - public class ToggleChecker { - public boolean isToggleEnabled(String toggleName, boolean fallback) { - Map toggleMap = new HashMap<>(); - toggleMap.put("FEATURE_TOGGLE1", true); - toggleMap.put("FEATURE_TOGGLE2", true); - toggleMap.put("FEATURE_TOGGLE3", false); - return toggleMap.containsKey(toggleName); - } - } - """, - SourceSpec::skip - ), - java( - """ - import com.osd.util.ToggleChecker; - class Foo { - private ToggleChecker checker = new ToggleChecker(); - void bar() { - if (checker.isToggleEnabled("FEATURE_TOGGLE1", false)) { - // Application code to show the feature - System.out.println("Feature is on"); - } - else { - // The code to run if the feature is off - System.out.println("Feature is off"); - } - } - } - """, - """ - class Foo { - void bar() { - // Application code to show the feature - System.out.println("Feature is on"); - } - } - """ - ) - ); - } } diff --git a/src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly6Test.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly6Test.java similarity index 97% rename from src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly6Test.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly6Test.java index d1fd1a1..0770412 100644 --- a/src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly6Test.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly6Test.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; @@ -33,7 +33,7 @@ class UpgradeLaunchDarkly6Test implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipeFromResource("/META-INF/rewrite/launchdarkly-6.yml", "org.openrewrite.launchdarkly.UpgradeLaunchDarkly6") + spec.recipeFromResource("/META-INF/rewrite/launchdarkly-6.yml", "org.openrewrite.featureflags.launchdarkly.UpgradeLaunchDarkly6") .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "launchdarkly-java-server-sdk-5")); } diff --git a/src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly7Test.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly7Test.java similarity index 96% rename from src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly7Test.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly7Test.java index c740a15..78f4ffe 100644 --- a/src/test/java/org/openrewrite/launchdarkly/UpgradeLaunchDarkly7Test.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/UpgradeLaunchDarkly7Test.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly; +package org.openrewrite.featureflags.launchdarkly; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -37,9 +37,9 @@ class UpgradeLaunchDarkly7Test implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipe(Environment.builder() - .scanRuntimeClasspath("org.openrewrite.launchdarkly") + .scanRuntimeClasspath("org.openrewrite.featureflags") .build() - .activateRecipes("org.openrewrite.launchdarkly.UpgradeLaunchDarkly7")) + .activateRecipes("org.openrewrite.featureflags.launchdarkly.UpgradeLaunchDarkly7")) .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "launchdarkly-java-server-sdk-6")); } diff --git a/src/test/java/org/openrewrite/launchdarkly/search/FindFeatureFlagTest.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlagTest.java similarity index 99% rename from src/test/java/org/openrewrite/launchdarkly/search/FindFeatureFlagTest.java rename to src/test/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlagTest.java index 6e0286f..cd33c66 100644 --- a/src/test/java/org/openrewrite/launchdarkly/search/FindFeatureFlagTest.java +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/search/FindFeatureFlagTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.launchdarkly.search; +package org.openrewrite.featureflags.launchdarkly.search; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; diff --git a/src/test/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValueTest.java b/src/test/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValueTest.java new file mode 100644 index 0000000..e8dde3c --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/openfeature/RemoveGetBooleanValueTest.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.featureflags.openfeature; + +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 RemoveGetBooleanValueTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveGetBooleanValue("flag-key-123abc", true)) + .parser(JavaParser.fromJavaVersion().classpath("sdk-1.9.0")); + } + + @Test + @DocumentExample + void removeGetBooleanValue() { + rewriteRun( + //language=java + java( + """ + import dev.openfeature.sdk.Client; + + class Foo { + void bar(Client client) { + if (client.getBooleanValue("flag-key-123abc", false)) { + System.out.println("Feature enabled"); + } else { + System.out.println("Feature disabled"); + } + } + } + """, + """ + import dev.openfeature.sdk.Client; + + class Foo { + void bar(Client client) { + System.out.println("Feature enabled"); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/featureflags/search/FindFeatureFlagTest.java b/src/test/java/org/openrewrite/featureflags/search/FindFeatureFlagTest.java new file mode 100644 index 0000000..506ea80 --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/search/FindFeatureFlagTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023 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.featureflags.search; + +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 FindFeatureFlagTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + //language=java + spec.parser(JavaParser.fromJavaVersion().dependsOn( + """ + package com.acme; + public class FeatureFlag { + public boolean isEnabled(String key) { + return false; + } + } + """ + ) + ); + } + + @Test + @DocumentExample + void findFeatureFlagWithAnyKey() { + rewriteRun( + spec -> spec.recipe(new FindFeatureFlag("com.acme.FeatureFlag isEnabled(String)", null)), + //language=java + java( + """ + import com.acme.FeatureFlag; + + class Test { + public void a() { + FeatureFlag client = new FeatureFlag(); + boolean flagValue = client.isEnabled("flag-key-123abc"); + if (flagValue) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + } + } + """, + """ + import com.acme.FeatureFlag; + + class Test { + public void a() { + FeatureFlag client = new FeatureFlag(); + boolean flagValue = /*~~>*/client.isEnabled("flag-key-123abc"); + if (flagValue) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + } + } + """ + ) + ); + } + + @Test + void findFlagWithSpecificKeyThroughFlow() { + rewriteRun( + spec -> spec.recipe(new FindFeatureFlag("com.acme.FeatureFlag isEnabled(String)", "flag-key-123abc")), + //language=java + java( + """ + import com.acme.FeatureFlag; + + class Test { + private static final String FEATURE_FLAG = "flag-key-123abc"; + private static final String FEATURE2_FLAG = "flag-key-789def"; + public void a() { + FeatureFlag client = new FeatureFlag(); + boolean flagValue = client.isEnabled(FEATURE_FLAG); + if (flagValue) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + boolean flagValue2 = client.isEnabled(FEATURE2_FLAG); + if (flagValue2) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + } + } + """, + """ + import com.acme.FeatureFlag; + + class Test { + private static final String FEATURE_FLAG = "flag-key-123abc"; + private static final String FEATURE2_FLAG = "flag-key-789def"; + public void a() { + FeatureFlag client = new FeatureFlag(); + boolean flagValue = /*~~>*/client.isEnabled(FEATURE_FLAG); + if (flagValue) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + boolean flagValue2 = client.isEnabled(FEATURE2_FLAG); + if (flagValue2) { + // Application code to show the feature + } else { + // The code to run if the feature is off + } + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/featureflags/unleash/RemoveIsEnabledTest.java b/src/test/java/org/openrewrite/featureflags/unleash/RemoveIsEnabledTest.java new file mode 100644 index 0000000..5ca35a1 --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/unleash/RemoveIsEnabledTest.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.featureflags.unleash; + +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 RemoveIsEnabledTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveIsEnabled("flag-key-123abc", true)) + .parser(JavaParser.fromJavaVersion().classpath("unleash-client-java")); + } + + @Test + @DocumentExample + void removeIsEnabled() { + rewriteRun( + //language=java + java( + """ + import io.getunleash.Unleash; + + class Test { + void bar(Unleash unleash) { + if (unleash.isEnabled("flag-key-123abc")) { + System.out.println("Feature enabled"); + } else { + System.out.println("Feature disabled"); + } + } + } + """, + """ + import io.getunleash.Unleash; + + class Test { + void bar(Unleash unleash) { + System.out.println("Feature enabled"); + } + } + """ + ) + ); + } +}