From 09ff4732bfaf12b89d0491e77b65ad4d8393c731 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 12 Sep 2024 22:06:39 +0200 Subject: [PATCH 1/3] Remove String feature flags for LaunchDarkly --- .../featureflags/RemoveBooleanFlag.java | 3 +- .../featureflags/RemoveStringFlag.java | 90 +++++++++++++++ .../launchdarkly/RemoveStringVariation.java | 58 ++++++++++ .../featureflags/RemoveBooleanFlagTest.java | 2 + .../featureflags/RemoveStringFlagTest.java | 105 ++++++++++++++++++ .../RemoveStringVariationTest.java | 65 +++++++++++ 6 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/openrewrite/featureflags/RemoveStringFlag.java create mode 100644 src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariation.java create mode 100644 src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java create mode 100644 src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariationTest.java diff --git a/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java b/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java index cc53893..a6a8dc2 100644 --- a/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java +++ b/src/main/java/org/openrewrite/featureflags/RemoveBooleanFlag.java @@ -72,7 +72,8 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) doAfterVisit(new SimplifyConstantIfBranchExecution().getVisitor()); doAfterVisit(new RemoveUnusedLocalVariables(null).getVisitor()); doAfterVisit(new RemoveUnusedPrivateFields().getVisitor()); - return new J.Literal(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, replacementValue, String.valueOf(replacementValue), null, JavaType.Primitive.Boolean); + J.Literal literal = new J.Literal(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, replacementValue, String.valueOf(replacementValue), null, JavaType.Primitive.Boolean); + return literal.withPrefix(mi.getPrefix()); } return mi; } diff --git a/src/main/java/org/openrewrite/featureflags/RemoveStringFlag.java b/src/main/java/org/openrewrite/featureflags/RemoveStringFlag.java new file mode 100644 index 0000000..4c5c0e5 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/RemoveStringFlag.java @@ -0,0 +1,90 @@ +/* + * 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; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.analysis.constantfold.ConstantFold; +import org.openrewrite.analysis.util.CursorUtil; +import org.openrewrite.java.JavaVisitor; +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.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Markers; +import org.openrewrite.staticanalysis.RemoveUnusedLocalVariables; +import org.openrewrite.staticanalysis.RemoveUnusedPrivateFields; +import org.openrewrite.staticanalysis.SimplifyConstantIfBranchExecution; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveStringFlag extends Recipe { + + @Override + public String getDisplayName() { + return "Remove a boolean feature flag for feature key"; + } + + @Override + public String getDescription() { + 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") + String featureKey; + + @Option(displayName = "Replacement value", + description = "The value to replace the feature flag check with.", + example = "topic-456") + String replacementValue; + + @Override + public TreeVisitor getVisitor() { + final MethodMatcher methodMatcher = new MethodMatcher(methodPattern, true); + JavaVisitor visitor = new JavaVisitor() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + if (methodMatcher.matches(mi) && isFeatureKey(mi.getArguments().get(0))) { + doAfterVisit(new SimplifyConstantIfBranchExecution().getVisitor()); + doAfterVisit(new RemoveUnusedLocalVariables(null).getVisitor()); + doAfterVisit(new RemoveUnusedPrivateFields().getVisitor()); + J.Literal literal = new J.Literal(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, replacementValue, '"' + replacementValue + '"', null, JavaType.Primitive.String); + return literal.withPrefix(mi.getPrefix()); + } + return mi; + } + + private boolean isFeatureKey(Expression firstArgument) { + return CursorUtil.findCursorForTree(getCursor(), firstArgument) + .bind(c -> ConstantFold.findConstantLiteralValue(c, String.class)) + .map(featureKey::equals) + .orSome(false); + } + }; + return Preconditions.check(new UsesMethod<>(methodMatcher), visitor); + } +} diff --git a/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariation.java b/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariation.java new file mode 100644 index 0000000..12a8e76 --- /dev/null +++ b/src/main/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariation.java @@ -0,0 +1,58 @@ +/* + * 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.launchdarkly; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.featureflags.RemoveBooleanFlag; +import org.openrewrite.featureflags.RemoveStringFlag; + +import java.util.Collections; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RemoveStringVariation 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 = "topic-456") + String replacementValue; + + @Override + public List getRecipeList() { + return Collections.singletonList(new RemoveStringFlag( + "com.launchdarkly.sdk.server.LDClient stringVariation(String, com.launchdarkly.sdk.*, String)", + featureKey, replacementValue)); + } +} diff --git a/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java b/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java index 117f613..e2bf810 100644 --- a/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java +++ b/src/test/java/org/openrewrite/featureflags/RemoveBooleanFlagTest.java @@ -16,6 +16,7 @@ package org.openrewrite.featureflags; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; @@ -25,6 +26,7 @@ class RemoveBooleanFlagTest implements RewriteTest { @Test + @DocumentExample void customMethodPatternForWrapper() { rewriteRun( spec -> spec.recipe(new RemoveBooleanFlag("com.acme.bank.CustomLaunchDarklyWrapper featureFlagEnabled(String, boolean)", "flag-key-123abc", true)), diff --git a/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java new file mode 100644 index 0000000..78eea8d --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java @@ -0,0 +1,105 @@ +/* + * 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.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 org.openrewrite.test.SourceSpec; + +import static org.openrewrite.java.Assertions.java; + +class RemoveStringFlagTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveStringFlag("com.acme.bank.InHouseFF getStringFeatureFlagValue(String, String)", "flag-key-123abc", "topic-456")) + // language=java + .parser(JavaParser.fromJavaVersion().dependsOn( + """ + package com.acme.bank; + public class InHouseFF { + public String getStringFeatureFlagValue(String key, String fallback) { + return fallback; + } + } + """ + )); + } + + @DocumentExample + @Test + void removeStringFeatureFlag() { + rewriteRun( + spec -> spec.recipe(new RemoveStringFlag("com.acme.bank.InHouseFF getStringFeatureFlagValue(String, String)", "flag-key-123abc", "topic-456")), + // language=java + java( + """ + import com.acme.bank.InHouseFF; + class Foo { + private InHouseFF inHouseFF = new InHouseFF(); + void bar() { + String topic = inHouseFF.getStringFeatureFlagValue("flag-key-123abc", "topic-123"); + System.out.println("Publishing to topic: " + topic); + } + } + """, + """ + class Foo { + void bar() { + String topic = "topic-456"; + System.out.println("Publishing to topic: " + topic); + } + } + """ + ) + ); + } + + @Disabled("This test is disabled because it is not yet implemented.") + @Test + void removeEqualsComparison() { + rewriteRun( + spec -> spec.recipe(new RemoveStringFlag("com.acme.bank.InHouseFF getStringFeatureFlagValue(String, String)", "flag-key-123abc", "topic-456")), + // language=java + java( + """ + import com.acme.bank.InHouseFF; + class Foo { + private InHouseFF inHouseFF = new InHouseFF(); + void bar() { + if ("topic-456".equals(inHouseFF.getStringFeatureFlagValue("flag-key-123abc", "topic-123"))) { + // Application code to show the feature + System.out.println("Feature is on"); + } + } + } + """, + """ + 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/launchdarkly/RemoveStringVariationTest.java b/src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariationTest.java new file mode 100644 index 0000000..a83fc4b --- /dev/null +++ b/src/test/java/org/openrewrite/featureflags/launchdarkly/RemoveStringVariationTest.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.launchdarkly; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class RemoveStringVariationTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveStringVariation("flag-key-123abc", "topic-456")) + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "launchdarkly-java-server-sdk-6")); + } + + @Test + @DocumentExample + void replaceStringVariation() { + rewriteRun( + // language=java + java( + """ + import com.launchdarkly.sdk.LDContext; + import com.launchdarkly.sdk.server.LDClient; + class Foo { + private LDClient client = new LDClient("sdk-key-123abc"); + void bar() { + LDContext context = null; + String topic = client.stringVariation("flag-key-123abc", context, "topic-123"); + System.out.println("Publishing to topic: " + topic); + } + } + """, + """ + class Foo { + void bar() { + String topic = "topic-456"; + System.out.println("Publishing to topic: " + topic); + } + } + """ + ) + ); + } +} From 127ac1053f864f64ef316920a5a60d58acf6ca80 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 12 Sep 2024 22:12:28 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/org/openrewrite/featureflags/RemoveStringFlagTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java index 78eea8d..c5abf18 100644 --- a/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java +++ b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java @@ -21,8 +21,6 @@ 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; class RemoveStringFlagTest implements RewriteTest { From 436805d4d39fe14673d1aebb0fb3e1d7d60848db Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 12 Sep 2024 22:15:08 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/org/openrewrite/featureflags/RemoveStringFlagTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java index c5abf18..c0cbe04 100644 --- a/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java +++ b/src/test/java/org/openrewrite/featureflags/RemoveStringFlagTest.java @@ -21,6 +21,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; + import static org.openrewrite.java.Assertions.java; class RemoveStringFlagTest implements RewriteTest {