Skip to content

Commit

Permalink
Merge pull request #11 from openrewrite/feature/find-flag
Browse files Browse the repository at this point in the history
Add FindFeatureFlag recipe
  • Loading branch information
sambsnyd authored Nov 14, 2023
2 parents c5f0667 + 5ddf4d3 commit d9d9a06
Show file tree
Hide file tree
Showing 3 changed files with 487 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ val rewriteVersion = rewriteRecipe.rewriteVersion.get()
dependencies {
implementation(platform("org.openrewrite:rewrite-bom:$rewriteVersion"))
implementation("org.openrewrite:rewrite-java")
implementation("org.openrewrite.meta:rewrite-analysis:$rewriteVersion")
implementation("org.openrewrite.recipe:rewrite-java-dependencies:$rewriteVersion")

testImplementation("org.openrewrite:rewrite-java-17")
Expand Down
167 changes: 167 additions & 0 deletions src/main/java/org/openrewrite/launchdarkly/search/FindFeatureFlag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.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.analysis.InvocationMatcher;
import org.openrewrite.analysis.dataflow.DataFlowNode;
import org.openrewrite.analysis.dataflow.DataFlowSpec;
import org.openrewrite.analysis.dataflow.Dataflow;
import org.openrewrite.analysis.trait.expr.Expr;
import org.openrewrite.analysis.trait.expr.Literal;
import org.openrewrite.analysis.trait.expr.VarAccess;
import org.openrewrite.analysis.trait.variable.Variable;
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.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.SearchResult;

@Value
@EqualsAndHashCode(callSuper = true)
public class FindFeatureFlag extends Recipe {
@Option(displayName = "Flag Type",
description = "The feature flag's type.",
example = "Bool",
valid = {"Bool", "Double", "Int", "JsonValue", "String"})
@Nullable
FeatureFlagType flagType;

@Option(displayName = "Feature Key",
description = "The unique key for the feature flag.",
example = "flag-key-123abc")
@Nullable
String featureKey;

@Override
public String getDisplayName() {
return "Find a LaunchDarkly feature flag";
}

@Override
public String getDescription() {
return "Find a LaunchDarkly feature flag.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
MethodMatcher launchDarklyClientMatcher = new MethodMatcher("com.launchdarkly.sdk.server.LDClient *Variation(..)");
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
if (!launchDarklyClientMatcher.matches(m)) {
return m;
}

if (flagType != null && featureKey != null) {
MethodMatcher flagTypeMatcher = flagType.asMethodMatcher();
Boolean matchesFeatureKey = getCursor().getMessage("feature.found");
if (flagTypeMatcher.matches(m) && matchesFeatureKey != null && matchesFeatureKey) {
return SearchResult.found(m);
}
} else if (flagType != null) {
MethodMatcher flagTypeMatcher = flagType.asMethodMatcher();
if (flagTypeMatcher.matches(m)) {
return SearchResult.found(m);
}
} else if (featureKey != null) {
Boolean matchesFeatureKey = getCursor().getMessage("feature.found");
if (matchesFeatureKey != null && matchesFeatureKey) {
return SearchResult.found(m);
}
} else {
return SearchResult.found(m);
}

return m;
}

@Override
public Expression visitExpression(Expression expression, ExecutionContext ctx) {
Expression e = super.visitExpression(expression, ctx);
if (StringUtils.isBlank(featureKey)) {
return e;
}

InvocationMatcher matcher = InvocationMatcher.fromMethodMatcher(launchDarklyClientMatcher);
boolean found = Dataflow.startingAt(getCursor())
.findSinks(new DataFlowSpec() {
@Override
public boolean isSource(DataFlowNode srcNode) {
return srcNode.asExpr(VarAccess.class)
.map(VarAccess::getVariable)
.map(Variable::getAssignedValues)
.bind(assignedVariables -> {
if (assignedVariables.size() > 1) {
return fj.data.Option.none();
}
for (Expr e : assignedVariables) {
if (e instanceof Literal) {
Literal l = (Literal) e;
return l.getValue()
.map(featureKey::equals);
}
}
return fj.data.Option.none();
}).orElse(() -> srcNode
.asExpr(Literal.class)
.bind(Literal::getValue)
.map(featureKey::equals))
.orSome(false);
}

@Override
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 {
Bool("boolVariation"),
Double("doubleVariation"),
Int("intVariation"),
JsonValue("jsonValueVariation"),
String("stringVariation");

String methodName;

FeatureFlagType(String methodName) {
this.methodName = methodName;
}

public MethodMatcher asMethodMatcher() {
return new MethodMatcher("com.launchdarkly.sdk.server.LDClient " + methodName + "(..)");
}
}
}
Loading

0 comments on commit d9d9a06

Please sign in to comment.