Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.staticanalysis;

import lombok.Getter;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.search.SemanticallyEqual;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Statement;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static java.util.Collections.singleton;

@Getter
public class AllBranchesIdentical extends Recipe {

final String displayName = "All branches in a conditional should not have the same implementation";

final String description = "When every branch of an `if`/`else` chain executes the same code, " +
"the condition serves no purpose and the code block can be used directly.";

final Set<String> tags = singleton("RSPEC-S3923");

final Duration estimatedEffortPerOccurrence = Duration.ofMinutes(15);

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaVisitor<ExecutionContext>() {

@Override
public J visitIf(J.If if_, ExecutionContext ctx) {
J.If if__ = (J.If) super.visitIf(if_, ctx);

if (if__.getElsePart() == null) {
return if__;
}

List<Statement> bodies = new ArrayList<>();
J.If current = if__;

while (current != null) {
bodies.add(current.getThenPart());
if (current.getElsePart() == null) {
return if__;
}
Statement elseBody = current.getElsePart().getBody();
if (elseBody instanceof J.If) {
current = (J.If) elseBody;
} else {
bodies.add(elseBody);
current = null;
}
}

Statement first = bodies.get(0);
for (int i = 1; i < bodies.size(); i++) {
if (!SemanticallyEqual.areEqual(first, bodies.get(i))) {
return if__;
}
}

doAfterVisit(new RemoveUnneededBlock().getVisitor());
return first.withPrefix(if__.getPrefix());
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.staticanalysis;

import lombok.Getter;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.*;
import org.openrewrite.marker.Markers;

import java.time.Duration;
import java.util.Set;

import static java.util.Collections.singleton;

@Getter
public class CollapsibleIfStatements extends Recipe {

final String displayName = "Mergeable \"if\" statements should be combined";

final String description = "When an `if` statement body contains only another `if` with no `else`, " +
"the two conditions can be combined with `&&`. " +
"Merging the conditions reduces nesting and makes the code easier to read.";

final Set<String> tags = singleton("RSPEC-S1066");

final Duration estimatedEffortPerOccurrence = Duration.ofMinutes(5);

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaVisitor<ExecutionContext>() {

@Override
public J visitIf(J.If if_, ExecutionContext ctx) {
J.If outerIf = (J.If) super.visitIf(if_, ctx);

if (outerIf.getElsePart() != null) {
return outerIf;
}

if (!(outerIf.getThenPart() instanceof J.Block)) {
return outerIf;
}
J.Block block = (J.Block) outerIf.getThenPart();
if (block.getStatements().size() != 1) {
return outerIf;
}

Statement onlyStatement = block.getStatements().get(0);
if (!(onlyStatement instanceof J.If)) {
return outerIf;
}
J.If innerIf = (J.If) onlyStatement;
if (innerIf.getElsePart() != null) {
return outerIf;
}

Expression outerCond = outerIf.getIfCondition().getTree();
Expression innerCond = innerIf.getIfCondition().getTree();

if (outerCond instanceof J.Binary && ((J.Binary) outerCond).getOperator() == J.Binary.Type.Or) {
outerCond = wrapInParens(outerCond);
}
if (innerCond instanceof J.Binary && ((J.Binary) innerCond).getOperator() == J.Binary.Type.Or) {
innerCond = wrapInParens(innerCond);
}

Expression combined = JavaElementFactory.newLogicalExpression(
J.Binary.Type.And,
outerCond,
innerCond.withPrefix(Space.SINGLE_SPACE)
);

J.If merged = outerIf
.withIfCondition(outerIf.getIfCondition().withTree(combined))
.withThenPart(innerIf.getThenPart().withPrefix(outerIf.getThenPart().getPrefix()));

return autoFormat(merged, ctx);
}

private Expression wrapInParens(Expression expr) {
return new J.Parentheses<>(
Tree.randomId(),
expr.getPrefix(),
Markers.EMPTY,
JRightPadded.build(expr.withPrefix(Space.EMPTY))
);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.staticanalysis;

import lombok.Getter;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.search.SemanticallyEqual;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.Statement;

import java.time.Duration;
import java.util.Set;

import static java.util.Collections.singleton;

@Getter
public class MergeIdenticalBranches extends Recipe {

final String displayName = "Branches with identical implementations should be merged";

final String description = "When two consecutive branches of an `if`/`else if` chain execute the same code, " +
"they can be merged by combining their conditions with `||`. " +
"This removes duplication and makes the intent clearer.";

final Set<String> tags = singleton("RSPEC-S1871");

final Duration estimatedEffortPerOccurrence = Duration.ofMinutes(10);

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaVisitor<ExecutionContext>() {

@Override
public J visitIf(J.If if_, ExecutionContext ctx) {
J.If if__ = (J.If) super.visitIf(if_, ctx);

// Only process the outermost if
if (getCursor().getParentTreeCursor().getValue() instanceof J.If.Else) {
return if__;
}

J.If merged = mergeConsecutiveIdentical(if__);
return merged != if__ ? merged : if__;
}

private J.If mergeConsecutiveIdentical(J.If outerIf) {
if (outerIf.getElsePart() == null) {
return outerIf;
}

Statement elseBody = outerIf.getElsePart().getBody();

if (elseBody instanceof J.If) {
J.If elseIf = (J.If) elseBody;

if (SemanticallyEqual.areEqual(outerIf.getThenPart(), elseIf.getThenPart())) {
// Merge: combine conditions with ||, skip the else-if
Expression combined = JavaElementFactory.newLogicalExpression(
J.Binary.Type.Or,
outerIf.getIfCondition().getTree(),
elseIf.getIfCondition().getTree().withPrefix(Space.SINGLE_SPACE)
);

J.If merged = outerIf
.withIfCondition(outerIf.getIfCondition().withTree(combined))
.withElsePart(elseIf.getElsePart());

// Continue merging in case the next branch also matches
return mergeConsecutiveIdentical(merged);
}

// Recurse into the rest of the chain
J.If rebuiltElseIf = mergeConsecutiveIdentical(elseIf);
if (rebuiltElseIf != elseIf) {
return outerIf.withElsePart(outerIf.getElsePart().withBody(rebuiltElseIf));
}
}

return outerIf;
}
};
}
}
Loading
Loading