Skip to content

Commit

Permalink
feat: Add replacement for assert(Not)True(null) checks (#22)
Browse files Browse the repository at this point in the history
Replace assertTrue null checks with assertNull and not null checks with assertNotNull.
  • Loading branch information
MartinWitt authored Jan 5, 2022
1 parent f892c8d commit 7b49994
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import com.google.common.flogger.FluentLogger;

import spoon.Launcher;
import spoon.compiler.Environment;
import spoon.processing.ProcessingManager;
Expand All @@ -27,6 +29,8 @@
import xyz.keksdose.spoon.code_solver.spoon.ImportCleaner;
import xyz.keksdose.spoon.code_solver.spoon.ImportComparator;
import xyz.keksdose.spoon.code_solver.spoon.SelectiveForceImport;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertNotNullTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertNullTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertThatTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertionsTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.ExpectedExceptionRemoval;
Expand Down Expand Up @@ -65,6 +69,8 @@ protected void addInput(String path, Launcher launcher) {
}

protected void addProcessors(ProcessingManager pm, ChangeListener listener) {
pm.addProcessor(new AssertNullTransformation(listener));
pm.addProcessor(new AssertNotNullTransformation(listener));
pm.addProcessor(new UnusedAssignment(listener));
pm.addProcessor(new UnusedLocalVariable(listener));
pm.addProcessor(new AssertThatTransformation(listener));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

import java.util.ArrayList;
import java.util.List;

import spoon.experimental.CtUnresolvedImport;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtImport;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtAbstractImportVisitor;

Expand Down Expand Up @@ -35,6 +37,14 @@ public static void removeImport(String importString, boolean isStatic, CtCompila
removalableImports.add(ctImport);
}
}
if (ctImport.getReference() instanceof CtExecutableReference) {
CtExecutableReference<?> executableReference = (CtExecutableReference<?>) ctImport.getReference();
String simpleName = executableReference.getSimpleName()
.substring(executableReference.getSimpleName().lastIndexOf('.') + 1);
if (executableReference.getSimpleName().equals(simpleName)) {
removalableImports.add(ctImport);
}
}
}
removalableImports.forEach(imports::remove);
removalableImports.forEach(CtImport::delete);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

package xyz.keksdose.spoon.code_solver.transformations.junit;

import java.util.List;

import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtType;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.TypeFilter;
import xyz.keksdose.spoon.code_solver.history.Change;
import xyz.keksdose.spoon.code_solver.history.ChangeListener;
import xyz.keksdose.spoon.code_solver.transformations.ImportHelper;
import xyz.keksdose.spoon.code_solver.transformations.TransformationProcessor;

public class AssertNotNullTransformation extends TransformationProcessor<CtInvocation<?>> {

public AssertNotNullTransformation(ChangeListener listener) {
super(listener);
}

@Override
public void process(CtInvocation<?> element) {
if (element.getExecutable() != null && JunitHelper.isJunit5AssertTrue(element.getExecutable())) {
CtInvocation<?> junit5AssertTrue = element;
CtExpression<?> expression = element.getArguments().iterator().next();
if (expression instanceof CtBinaryOperator) {
CtBinaryOperator<?> binaryOperator = (CtBinaryOperator<?>) expression;
if (binaryOperator.getKind().equals(BinaryOperatorKind.NE)) {
CtExpression<?> check = findTestingExpression(binaryOperator);
if (check != null) {
CtInvocation<?> junit5AssertNotNull = createJunit5AssertNotNull(check);
junit5AssertNotNull.setComments(element.getComments());
junit5AssertTrue.replace(junit5AssertNotNull);
if (element.getArguments().size() == 2) {
// readd the String if it fails argument
junit5AssertNotNull.addArgument(element.getArguments().get(1));
}
adjustImports(element);
notifyChangeListener(element, junit5AssertTrue);
}
}
}
}
}

private CtExpression<?> findTestingExpression(CtBinaryOperator<?> binaryOperator) {
CtExpression<?> left = binaryOperator.getLeftHandOperand();
CtExpression<?> right = binaryOperator.getRightHandOperand();
CtExpression<?> check = null;
if (isNullType(left)) {
check = right;
}
if (isNullType(right)) {
check = left;
}
return check;
}

private void adjustImports(CtInvocation<?> element) {
CtType<?> parent = element.getParent(CtType.class);
CtCompilationUnit compilationUnit = element.getPosition().getCompilationUnit();

if (parent != null && !hasJunit5AsserTrueLeft(parent)) {
ImportHelper.removeImport("org.junit.jupiter.api.Assertions.assertTrue", true, compilationUnit);
}
ImportHelper.addImport("org.junit.jupiter.api.Assertions.assertNotNull", true, compilationUnit);

}

private boolean hasJunit5AsserTrueLeft(CtType<?> parent) {
return parent.getElements(new TypeFilter<>(CtInvocation.class))
.stream()
.filter(v -> v.getExecutable() != null)
.anyMatch(v -> JunitHelper.isJunit5AssertTrue(v.getExecutable()));
}

private boolean isNullType(CtExpression<?> left) {
return left.getType() != null && left.getType().equals(getFactory().Type().nullType());
}

private CtInvocation<?> createJunit5AssertNotNull(CtExpression<?> check) {
CtTypeReference<?> typeRef = getFactory().Type().createReference("org.junit.jupiter.api.Assertions");
CtTypeReference<?> voidType = getFactory().Type().voidPrimitiveType();
CtExecutableReference<?> assertNotNull = getFactory().Executable()
.createReference(typeRef, voidType, "assertNotNull", List.of(getFactory().Type().objectType()));
return getFactory().createInvocation(null, assertNotNull, List.of(check));
}

private void notifyChangeListener(CtInvocation<?> oldAssert, CtInvocation<?> newAssert) {
CtType<?> parent = newAssert.getParent(CtType.class);
setChanged(parent, new Change(String.format("Replaced %s with %s", oldAssert, newAssert),
"AssertTrue instead of AssertNotNull", parent));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

package xyz.keksdose.spoon.code_solver.transformations.junit;

import java.util.List;

import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtType;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.TypeFilter;
import xyz.keksdose.spoon.code_solver.history.Change;
import xyz.keksdose.spoon.code_solver.history.ChangeListener;
import xyz.keksdose.spoon.code_solver.transformations.ImportHelper;
import xyz.keksdose.spoon.code_solver.transformations.TransformationProcessor;

public class AssertNullTransformation extends TransformationProcessor<CtInvocation<?>> {

public AssertNullTransformation(ChangeListener listener) {
super(listener);
}

@Override
public void process(CtInvocation<?> invocation) {
if (invocation.getExecutable() != null && JunitHelper.isJunit5AssertTrue(invocation.getExecutable())) {
CtInvocation<?> junit5AssertTrue = invocation;
CtExpression<?> expression = invocation.getArguments().iterator().next();
if (expression instanceof CtBinaryOperator) {
CtBinaryOperator<?> binaryOperator = (CtBinaryOperator<?>) expression;
if (binaryOperator.getKind().equals(BinaryOperatorKind.EQ)) {
CtExpression<?> check = findTestingExpression(binaryOperator);
if (check != null) {
CtInvocation<?> junit5AssertNull = createJunit5AssertNull(check);
junit5AssertNull.setComments(invocation.getComments());
junit5AssertTrue.replace(junit5AssertNull);
if (invocation.getArguments().size() == 2) {
// readd the String if it fails argument
junit5AssertNull.addArgument(invocation.getArguments().get(1));
}
adjustImports(invocation);
notifyChangeListener(invocation, junit5AssertTrue);
}
}
}
}
}

private CtExpression<?> findTestingExpression(CtBinaryOperator<?> binaryOperator) {
CtExpression<?> left = binaryOperator.getLeftHandOperand();
CtExpression<?> right = binaryOperator.getRightHandOperand();
CtExpression<?> check = null;
if (isNullType(left)) {
check = right;
}
if (isNullType(right)) {
check = left;
}
return check;
}

private void adjustImports(CtInvocation<?> element) {
CtType<?> parent = element.getParent(CtType.class);
CtCompilationUnit compilationUnit = element.getPosition().getCompilationUnit();

if (parent != null && !hasJunit5AsserTrueLeft(parent)) {
ImportHelper.removeImport("org.junit.jupiter.api.Assertions.assertTrue", true, compilationUnit);
}
ImportHelper.addImport("org.junit.jupiter.api.Assertions.assertNull", true, compilationUnit);

}

private boolean hasJunit5AsserTrueLeft(CtType<?> parent) {
return parent.getElements(new TypeFilter<>(CtInvocation.class))
.stream()
.filter(v -> v.getExecutable() != null)
.anyMatch(v -> JunitHelper.isJunit5AssertTrue(v.getExecutable()));
}

private boolean isNullType(CtExpression<?> left) {
return left.getType() != null && left.getType().equals(getFactory().Type().nullType());
}

private CtInvocation<?> createJunit5AssertNull(CtExpression<?> check) {
CtTypeReference<?> typeRef = getFactory().Type().createReference("org.junit.jupiter.api.Assertions");
CtTypeReference<?> voidType = getFactory().Type().voidPrimitiveType();
CtExecutableReference<?> assertNull = getFactory().Executable()
.createReference(typeRef, voidType, "assertNull", List.of(getFactory().Type().objectType()));
return getFactory().createInvocation(null, assertNull, List.of(check));
}

private void notifyChangeListener(CtInvocation<?> oldAssert, CtInvocation<?> newAssert) {
CtType<?> parent = newAssert.getParent(CtType.class);
setChanged(parent, new Change(String.format("Replaced %s with %s", oldAssert, newAssert),
"AssertTrue instead of AssertNull", parent));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtExecutableReference;

public class JunitHelper {

Expand Down Expand Up @@ -103,4 +104,12 @@ public static Optional<CtAnnotation<?>> getIgnoreAnnotation(CtMethod<?> method)
public static CtAnnotation<?> createDisableAnnotation(Factory factory) {
return factory.createAnnotation(factory.createReference("org.junit.jupiter.api.Disabled"));
}

public static boolean isJunit5AssertTrue(CtExecutableReference<?> executable) {
if (executable != null && executable.getDeclaringType() != null) {
return executable.getDeclaringType().getQualifiedName().equals("org.junit.jupiter.api.Assertions")
&& executable.getSimpleName().equals("assertTrue");
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import xyz.keksdose.spoon.code_solver.transformations.junit.AssertNotNullTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertNullTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.AssertionsTransformation;
import xyz.keksdose.spoon.code_solver.transformations.junit.TestAnnotation;

Expand Down Expand Up @@ -90,4 +92,42 @@ public void printerDoesNotIncludeWhiteSpaceInInvocations(@TempDir File tempRoot)
assertThat(result).doesNotContain("import org.junit.Test;");
assertThat(result).doesNotContain("import org.junit.Assert.");
}

@Test
void replaceAssertTrueNotNullCheck(@TempDir File tempRoot) throws IOException {
String fileName = "AssertNotNull.java";
String resourcePath = "projects/junittests/AssertNotNull.java";
File copy = TestHelper.createCopy(tempRoot, resourcePath, fileName);
List<TransformationCreator> transformations = createProcessorSupplier(v -> new TestAnnotation(v),
v -> new AssertionsTransformation(v), v -> new AssertNotNullTransformation(v));
new TransformationHelper.Builder().path(tempRoot.getAbsolutePath())
.className("AssertNotNull")
.processors(transformations)
.apply();
String result = Files.readString(copy.toPath());
assertThat(result).contains("@Test");
assertThat(result).contains("import org.junit.jupiter.api.Test;");
assertThat(result).doesNotContain("import org.junit.Test;");
assertThat(result).doesNotContain("import org.junit.Assert.");
assertThat(result).doesNotContain("!= null");
}

@Test
void replaceAssertTrueNullCheck(@TempDir File tempRoot) throws IOException {
String fileName = "AssertNotNull.java";
String resourcePath = "projects/junittests/AssertNotNull.java";
File copy = TestHelper.createCopy(tempRoot, resourcePath, fileName);
List<TransformationCreator> transformations = createProcessorSupplier(v -> new TestAnnotation(v),
v -> new AssertionsTransformation(v), v -> new AssertNullTransformation(v));
new TransformationHelper.Builder().path(tempRoot.getAbsolutePath())
.className("AssertNotNull")
.processors(transformations)
.apply();
String result = Files.readString(copy.toPath());
assertThat(result).contains("@Test");
assertThat(result).contains("import org.junit.jupiter.api.Test;");
assertThat(result).doesNotContain("import org.junit.Test;");
assertThat(result).doesNotContain("import org.junit.Assert.");
assertThat(result).doesNotContain("== null");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package projects.junittests;

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

public class AssertNotNull {

@Test
public void test() {
assertTrue(Integer.valueOf("3") != null);
assertTrue(Integer.valueOf("3") != null, "String Test");
assertTrue(true);
assertTrue(Integer.valueOf("3") == null);
assertTrue(Integer.valueOf("3") == null, "String Test");
}
}

0 comments on commit 7b49994

Please sign in to comment.