Skip to content

Commit

Permalink
feature: ChangeLocalVariableName + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Jan 30, 2017
1 parent 27b0e77 commit f5e67a3
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 54 deletions.
52 changes: 34 additions & 18 deletions src/main/java/spoon/refactoring/ChangeLocalVariableName.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
import java.util.List;
import java.util.regex.Pattern;

import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.reference.CtReference;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.chain.CtConsumer;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.query.VariableReferenceQuery;
import spoon.reflect.visitor.query.VariableScopeQuery;
import spoon.reflect.visitor.filter.ParentFunction;
import spoon.reflect.visitor.filter.VariableReferenceFunction;
import spoon.reflect.visitor.filter.VariableScopeFunction;

public class ChangeLocalVariableName extends AbstractRenameRefactor<CtLocalVariable<?>> {

Expand All @@ -39,37 +42,50 @@ public ChangeLocalVariableName() {

@Override
protected void forEachReference(CtConsumer<CtReference> consumer) {
getTarget().map(new VariableReferenceQuery()).forEach(consumer);
getTarget().map(new VariableReferenceFunction()).forEach(consumer);
}

@Override
protected void detectNameConflicts(List<Issue> issues) {
//search for conflicts in scope of parent statement list
CtStatementList l_scope = getTarget().getParent(CtStatementList.class);
//search for parent element, which represents scope of variables, which might be in conflict
//it means first Method, Constructor or AnonymousExecutable. The Lambda is not scope for variables.
CtExecutable l_scope = getTarget().map(new ParentFunction()).select(new Filter<CtExecutable<?>>() {
@Override
public boolean matches(CtExecutable<?> element) {
if (element instanceof CtLambda) {
return false;
}
return true;
}
}).first();
//the helper query which searches in scope of input variable for declaration of target variable
CtQuery scopeQuery = target.getFactory().createQuery().map(new VariableScopeQuery().setFilter(new Filter<CtElement>() {
final CtQuery scopeQuery = target.getFactory().createQuery().map(new VariableScopeFunction()).select(new Filter<CtElement>() {
public boolean matches(CtElement element) {
return element == target;
};
}));
//search for all variable declarations with newName - potential conflict
l_scope.filterChildren(new Filter<CtLocalVariable<?>>() {
});
//search for all variable declarations whose name is equal to newName => Search for potential name conflict
l_scope.filterChildren(new Filter<CtVariable<?>>() {
@Override
public boolean matches(CtLocalVariable<?> var) {
public boolean matches(CtVariable<?> var) {
if (newName.equals(var.getSimpleName())) {
/*
* visit all elements in scope of input variable
* and match them if they are target variable
* We have found a CtVariable whose simple name is equal to newName of target variable.
* Check if these variables are in conflict by
* visiting all elements in scope of input variable
* and match them if they are target variable.
* In other words, search for declaration of target variable in scope of input variable.
* If it is found, then there is conflict
*/
return scopeQuery.setInput(var).list().size() > 0;
return scopeQuery.setInput(var).first() != null;
}
return false;
}
})
//called for each variable declaration which is in conflict with newName
.forEach(new CtConsumer<CtLocalVariable<?>>() {
.forEach(new CtConsumer<CtVariable<?>>() {
@Override
public void accept(CtLocalVariable<?> conflictVar) {
public void accept(CtVariable<?> conflictVar) {
Issue issue = createNameConflictIssue(conflictVar);
if (issue != null) {
issues.add(issue);
Expand All @@ -78,7 +94,7 @@ public void accept(CtLocalVariable<?> conflictVar) {
});
}

protected Issue createNameConflictIssue(CtLocalVariable<?> conflictVar) {
return new IssueImpl("Local variable with name " + conflictVar.getSimpleName() + " already exists.");
protected Issue createNameConflictIssue(CtVariable<?> conflictVar) {
return new IssueImpl(conflictVar.getSimpleName() + " with name " + conflictVar.getSimpleName() + " already exists.");
}
}
152 changes: 120 additions & 32 deletions src/test/java/spoon/test/refactoring/ChangeVariableNameTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,160 @@

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Before;
import org.junit.Test;

import spoon.Launcher;
import spoon.SpoonException;
import spoon.refactoring.ChangeLocalVariableName;
import spoon.refactoring.Refactoring;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.reflect.visitor.filter.NameFilter;
import spoon.test.refactoring.testclasses.VariableRename;

public class ChangeVariableNameTest
{
@Test
public void testRenameLocalVariable() throws Exception {
Factory factory;
CtClass<?> VariableRenameClass;
CtLocalVariable<?> local1Var;

@Before
public void setup() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {
"-i", "src/test/java/spoon/test/refactoring/testclasses",
"-o", "target/spooned/refactoring"
"-o", "target/spooned/refactoring",
"--level","info"
});
launcher.buildModel();
Factory factory = launcher.getFactory();
CtClass<?> VariableRenameClass = factory.Class().get(VariableRename.class);

List<CtLocalVariable<?>> localVars = VariableRenameClass.getElements(new TypeFilter<CtLocalVariable<?>>(CtLocalVariable.class));
CtLocalVariable local1Var = localVars.get(0);
launcher.getEnvironment().setCommentEnabled(true);
launcher.addInputResource("./src/test/java/spoon/test/refactoring/testclasses");
launcher.run();
factory = launcher.getFactory();
VariableRenameClass = factory.Class().get(VariableRename.class);
local1Var = VariableRenameClass.filterChildren(new NameFilter<>("local1")).first();
assertEquals("local1", local1Var.getSimpleName());
}

@Test
public void testModelConsistency() throws Exception {
new VariableRename();
}

/*
* the pair of String and Boolean.
* The String is new local1 variable name
* The Boolean defines whether this rename must fail on name conflict
*/
Object[] renameConflict = new Object[]{
"local0", false,
"local1", false,
"local2", false,
"local3", false,
"local4", false,
"local5", false,
"local6", false,
"local7", false,
"local8", false,
"fnc", false,
"values", false,
"param1", false,
"method1", false,
"staticMethod1", false,
"method", false,
};

@Test
public void testRenameLocalVariableToUsedName() throws Exception {

ChangeLocalVariableName refactor = new ChangeLocalVariableName();
refactor.setTarget(local1Var);
refactor.setNewName("local2");
try {
refactor.refactor();
fail();
} catch(SpoonException e) {

int num = renameConflict.length/2;
int i=0;
i=2; num=i+1;
for (; i<num; i++) {
String newName = (String)renameConflict[2*i];
boolean shouldFail = (Boolean)renameConflict[2*i+1];
refactor.setNewName(newName);
String lastName = local1Var.getSimpleName();
if(shouldFail) {
try {
refactor.refactor();
fail("Rename of local1 should fail when trying rename to \""+newName+"\"");
} catch(SpoonException e) {
}
assertEquals("Rename of local1 failed when trying rename to \""+newName+"\" but the name of variable should not be changed", lastName, local1Var.getSimpleName());
} else {
try {
refactor.refactor();
} catch(SpoonException e) {
throw new AssertionError("Rename of local1 should NOT fail when trying rename to \""+newName+"\"", e);
}
assertEquals("Rename of local1 to \""+newName+"\" passed, but the name of variable was not changed", newName, local1Var.getSimpleName());
}
printModelAndTestConsistency();
}
assertEquals("local1", local1Var.getSimpleName());
}

private void printModelAndTestConsistency() {
/*
* TODO
* 1) print modified model,
* 2) build it
* 3) create instance using that new model
* 4) test consistency of the class
*/
}

@Test
public void testRenameLocalVariableToSameName() throws Exception {

ChangeLocalVariableName refactor = new ChangeLocalVariableName();
refactor.setNewName("local1");
refactor.refactor();
assertEquals("local1", local1Var.getSimpleName());
}

@Test
public void testRenameLocalVariableToInvalidName() throws Exception {

ChangeLocalVariableName refactor = new ChangeLocalVariableName();
refactor.setNewName("");
try {
refactor.setNewName("");
refactor.refactor();
fail();
} catch(SpoonException e) {
}
assertEquals("local1", local1Var.getSimpleName());

refactor.setNewName("x ");
try {
refactor.refactor();
fail();
} catch(SpoonException e) {
}

refactor.setNewName("x y");
try {
refactor.refactor();
fail();
} catch(SpoonException e) {
}

refactor.setNewName("x(");
try {
refactor.refactor();
fail();
} catch(SpoonException e) {
}
}

@Test
public void testRenameLocalVariableToValidName() throws Exception {
ChangeLocalVariableName refactor = new ChangeLocalVariableName();
refactor.setNewName("local3");
refactor.refactor();
assertEquals("local3", local1Var.getSimpleName());


final CtClass<?> aClassX = (CtClass<?>) launcher.getFactory().Type().get("spoon.test.refactoring.testclasses.AClassX");

final CtBinaryOperator<?> instanceofInvocation = aClassX.getElements(new TypeFilter<CtBinaryOperator<?>>(CtBinaryOperator.class)).get(0);
assertEquals(BinaryOperatorKind.INSTANCEOF, instanceofInvocation.getKind());
assertEquals("o", instanceofInvocation.getLeftHandOperand().toString());
assertEquals("spoon.test.refactoring.testclasses.AClassX", instanceofInvocation.getRightHandOperand().toString());
}


}
Original file line number Diff line number Diff line change
@@ -1,14 +1,70 @@
package spoon.test.refactoring.testclasses;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

import org.junit.Assert;

public class VariableRename
{
String member1="m1";
static String static1="s1";

public Object method(String param1) {
String local1 = "l1";
String local2 = "l2";
return new Object[]{member1, static1, param1, local1, local2, method1(), staticMethod1()};
public VariableRename()
{
String[] result = method("p1");
Assert.assertArrayEquals(new String[]{"l10","l9","m1","s1","p1","l0","l1","l2","l3_0","l4","met1","statMet1","ex1","ex2","l7","l8"}, result);
}

public String[] method(String param1) {
List<String> values = new ArrayList<>();
String local10 = "l10";
values.add(local10);
new Runnable() {
@Override
public void run() {
String local9 = "l9";
values.add(local9);
Function<String, String> fnc = (String local7)->{
String local8 = "l8";
try {
throw new Exception("ex2");
} catch (Exception local6) {
values.add(member1);
values.add(static1);
values.add(param1);
{
String local0 = "l0";
values.add(local0);
}
String local1 = "l1";
values.add(local1);
String local2 = "l2";
values.add(local2);
for(int local3 = 0; local3<1; local3++)
{
values.add("l3_"+String.valueOf(local3));
String local4 = "l4";
values.add(local4);
}
values.add(method1());
values.add(staticMethod1());
try {
throw new Exception("ex1");
} catch (Exception local5) {
values.add(local5.getMessage());
}
values.add(local6.getMessage());
}
values.add(local7);
return local8;
};
values.add(fnc.apply("l7"));
}
}.run();
return values.toArray(new String[0]);
}

public String method1() {
Expand Down

0 comments on commit f5e67a3

Please sign in to comment.