-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding feature for removing unnecessary lambda expression block . #167
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ | |
import com.sun.source.tree.ExpressionTree; | ||
import com.sun.source.tree.IfTree; | ||
import com.sun.source.tree.ImportTree; | ||
import com.sun.source.tree.LambdaExpressionTree; | ||
import com.sun.source.tree.LiteralTree; | ||
import com.sun.source.tree.MemberSelectTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
|
@@ -129,6 +130,7 @@ public class XPFlagCleaner extends BugChecker | |
BugChecker.ReturnTreeMatcher, | ||
BugChecker.UnaryTreeMatcher, | ||
BugChecker.VariableTreeMatcher, | ||
BugChecker.LambdaExpressionTreeMatcher, | ||
BugChecker.MethodTreeMatcher { | ||
|
||
/** | ||
|
@@ -1038,6 +1040,36 @@ public Description matchVariable(VariableTree tree, VisitorState state) { | |
return Description.NO_MATCH; | ||
} | ||
|
||
// This method removes unnecessary blocks from lambda expression bodies. | ||
// This scenario is possible in case of single return statements and expression statements | ||
// in the lambda body after Piranha removes the code related to stale flags. | ||
@Override | ||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState visitorState) { | ||
Tree lambdaBody = tree.getBody(); | ||
if (lambdaBody instanceof BlockTree) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are more fundamental problems (see above), but you want |
||
BlockTree bt = (BlockTree) lambdaBody; | ||
// Block should contain only one statement | ||
if (bt.getStatements().size() == 1) { | ||
StatementTree singleStmt = bt.getStatements().get(0); | ||
ExpressionTree expr = null; | ||
if (singleStmt instanceof ReturnTree) { | ||
ReturnTree stmt = (ReturnTree) singleStmt; | ||
expr = stmt.getExpression(); | ||
} | ||
if (singleStmt instanceof ExpressionStatementTree) { | ||
ExpressionStatementTree expStmt = (ExpressionStatementTree) singleStmt; | ||
expr = expStmt.getExpression(); | ||
} | ||
if (expr != null) { | ||
return buildDescription(tree) | ||
.addFix(SuggestedFix.replace(bt, visitorState.getSourceForNode(expr))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be safer to build a new lambda expression with |
||
.build(); | ||
} | ||
} | ||
} | ||
return buildDescription(tree).build(); | ||
} | ||
|
||
/** Represents the character at the end of an enum constant */ | ||
enum EnumEnding { | ||
// Ignore the result of the trailing enum character, whether we know what it is or not | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -513,6 +513,92 @@ public void testIgnoresPrefixMatchFlag() throws IOException { | |
.doTest(); | ||
} | ||
|
||
// Tests for cleanup of unnecessary blocks in the body of a lambda expression. | ||
@Test | ||
public void testRemoveUnnecessaryBlockLambda() throws IOException { | ||
ErrorProneFlags.Builder b = ErrorProneFlags.builder(); | ||
b.putFlag("Piranha:FlagName", "STALE_FLAG"); | ||
b.putFlag("Piranha:IsTreated", "false"); | ||
b.putFlag("Piranha:Config", "config/properties.json"); | ||
|
||
BugCheckerRefactoringTestHelper bcr = | ||
BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); | ||
|
||
bcr = bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); | ||
bcr = PiranhaTestingHelpers.addHelperClasses(bcr); | ||
|
||
bcr.addInputLines( | ||
"TestRemoveBlocks.java", | ||
"package com.uber.piranha;", | ||
"import java.util.function.Consumer;", | ||
"import java.util.function.Supplier;", | ||
"class TestRemoveBlocks {", | ||
" private XPTest experimentation;", | ||
" public void testMethod(){", | ||
" Consumer<String> someConsumer = (s) -> {", | ||
" if(experimentation.isToggleDisabled(\"STALE_FLAG\")){", | ||
" System.out.println(s);", | ||
" } ", | ||
" else {", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably doesn't matter for a test, but this is a weird way to format the code vs |
||
" System.out.println(s + \"123\");", | ||
" }", | ||
" };", | ||
" Supplier<String> someSupplier = () -> {", | ||
" if(experimentation.isToggleDisabled(\"STALE_FLAG\")){", | ||
" return \"ABC\";", | ||
" } ", | ||
" else {", | ||
" return \"123\";", | ||
" }", | ||
" };", | ||
" Supplier<String> someSupplier1 = () -> {", | ||
" if(experimentation.isToggleDisabled(\"STALE_FLAG\")){", | ||
" System.out.println(\"ABC\");", | ||
" return \"ABC\";", | ||
" } ", | ||
" else {", | ||
" System.out.println(\"123\");", | ||
" return \"123\";", | ||
" }", | ||
" };", | ||
" Consumer<String> someConsumer1 = (s) -> {", | ||
" if(experimentation.isToggleDisabled(\"STALE_FLAG\")){", | ||
" System.out.println(s);", | ||
" } ", | ||
" else {", | ||
" System.out.println(s + \"123\");", | ||
" }", | ||
" System.out.println(\"No match\");", | ||
" };", | ||
" }", | ||
"}") | ||
.addOutputLines( | ||
"TestRemoveBlocks.java", | ||
"package com.uber.piranha;", | ||
"import java.util.function.Consumer;", | ||
"import java.util.function.Supplier;", | ||
"class TestRemoveBlocks {", | ||
" private XPTest experimentation;", | ||
" public void testMethod(){", | ||
" Consumer<String> someConsumer = (s) -> {", | ||
" System.out.println(s);", | ||
" };", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand. Aren't these blocks exactly what you are trying to remove? Shouldn't the rewrite here be:
? |
||
" Supplier<String> someSupplier = () -> {", | ||
" return \"ABC\";", | ||
" };", | ||
" Supplier<String> someSupplier1 = () -> {", | ||
" System.out.println(\"ABC\");", | ||
" return \"ABC\";", | ||
" };", | ||
" Consumer<String> someConsumer1 = (s) -> {", | ||
" System.out.println(s);", | ||
" System.out.println(\"No match\");", | ||
" };", | ||
" }", | ||
"}") | ||
.doTest(); | ||
} | ||
|
||
@Test | ||
public void testRemoveSpecificAPIpatternsMockito() throws IOException { | ||
|
||
|
@@ -954,137 +1040,6 @@ public void testRemoveSpecificAPIPatternsInstanceMethod() throws IOException { | |
.doTest(); | ||
} | ||
|
||
@Test | ||
public void testRemoveSpecificAPIpatterns() throws IOException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my previous PR, I had split this test case into smaller test case. But I had not deleted this test case. |
||
|
||
ErrorProneFlags.Builder b = ErrorProneFlags.builder(); | ||
b.putFlag("Piranha:FlagName", "STALE_FLAG"); | ||
b.putFlag("Piranha:IsTreated", "false"); | ||
b.putFlag("Piranha:Config", "config/properties.json"); | ||
|
||
BugCheckerRefactoringTestHelper bcr = | ||
BugCheckerRefactoringTestHelper.newInstance(new XPFlagCleaner(b.build()), getClass()); | ||
|
||
bcr = bcr.setArgs("-d", temporaryFolder.getRoot().getAbsolutePath()); | ||
bcr = PiranhaTestingHelpers.addHelperClasses(bcr); | ||
bcr.addInputLines( | ||
"XPFlagCleanerSinglePositiveCase.java", | ||
"package com.uber.piranha;", | ||
"import static org.mockito.Mockito.when;", | ||
"import org.mockito.invocation.InvocationOnMock;", | ||
"import org.mockito.stubbing.Answer;", | ||
"import org.mockito.stubbing.OngoingStubbing;", | ||
"import org.easymock.EasyMock;", | ||
"import static org.junit.Assert.assertFalse;", | ||
"import static org.junit.Assert.assertTrue;", | ||
"class MockitoTest {", | ||
" private XPTest experimentation;", | ||
" public void evaluate() {", | ||
"// Mockito Test Scenarios", | ||
" Answer ans = new Answer<Integer>() {\n" | ||
+ " public Integer answer(InvocationOnMock invocation) throws Throwable {\n" | ||
+ " return (Integer) invocation.getArguments()[0];\n" | ||
+ " }};", | ||
" when(experimentation.isToggleDisabled(\"STALE_FLAG\")).thenReturn(false);", | ||
" when(experimentation.isToggleEnabled(\"STALE_FLAG\")).thenThrow(new RuntimeException());", | ||
" when(experimentation.isToggleDisabled(\"STALE_FLAG\")).thenCallRealMethod();", | ||
" when(experimentation.isToggleEnabled(\"STALE_FLAG\")).then(ans);", | ||
" boolean b1 = someWrapper(when(experimentation.isToggleDisabled(\"STALE_FLAG\")).thenCallRealMethod());", | ||
" boolean b2 = someWrapper(when(experimentation.isToggleDisabled(\"OTHER_FLAG\")).thenCallRealMethod());", | ||
" someWhenWrapper(when(experimentation.isToggleDisabled(\"STALE_FLAG\"))).thenCallRealMethod();", | ||
" someWhenWrapper(when(experimentation.isToggleDisabled(\"OTHER_FLAG\"))).thenCallRealMethod();", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).thenReturn(false);", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).thenThrow(new RuntimeException());", | ||
" when(experimentation.isToggleDisabled(\"OTHER_FLAG\")).thenCallRealMethod();", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).then(ans);", | ||
" when(foobar()).thenReturn(false);", | ||
" when(foobar()).thenAnswer(ans);", | ||
"// Easymock Test scenarios", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"STALE_FLAG\")).andReturn(true).anyTimes();", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"STALE_FLAG\")).andReturn(true).times(5);", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"STALE_FLAG\")).andReturn(true).times(5, 6);", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"STALE_FLAG\")).andReturn(true).once();", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"STALE_FLAG\")).andReturn(true).atLeastOnce();", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"STALE_FLAG\")).andReturn(true).anyTimes();", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"STALE_FLAG\")).andReturn(true);", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"STALE_FLAG\")).asStub();", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"OTHER_FLAG\")).andReturn(true).anyTimes();", | ||
" EasyMock.expect(foobar()).andReturn(true).times(5);", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"OTHER_FLAG\")).andReturn(true).times(5, 6);", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).once();", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).atLeastOnce();", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).anyTimes();", | ||
"// JUnit Assert Test scearios", | ||
" assertFalse(experimentation.isToggleDisabled(\"STALE_FLAG\"));", | ||
" assertTrue(experimentation.isToggleEnabled(\"STALE_FLAG\"));", | ||
" assertFalse(experimentation.isToggleEnabled(\"OTHER_FLAG\"));", | ||
" assertTrue(experimentation.isToggleDisabled(\"OTHER_FLAG\"));", | ||
" assertTrue(foobar());", | ||
" }", | ||
" public boolean foobar() { return true;}", | ||
" public boolean someWrapper(Object obj) { return true;}", | ||
" public OngoingStubbing<Boolean> someWhenWrapper(OngoingStubbing<Boolean> x) { return x;}", | ||
"}") | ||
.addOutputLines( | ||
"XPFlagCleanerSinglePositiveCase.java", | ||
"package com.uber.piranha;", | ||
"import static org.mockito.Mockito.when;", | ||
"import org.mockito.invocation.InvocationOnMock;", | ||
"import org.mockito.stubbing.Answer;", | ||
"import org.mockito.stubbing.OngoingStubbing;", | ||
"import org.easymock.EasyMock;", | ||
"import static org.junit.Assert.assertFalse;", | ||
"import static org.junit.Assert.assertTrue;", | ||
"class MockitoTest {", | ||
" private XPTest experimentation;", | ||
" public void evaluate() {", | ||
"// Mockito Test Scenarios", | ||
" Answer ans = new Answer<Integer>() {\n" | ||
+ " public Integer answer(InvocationOnMock invocation) throws Throwable {\n" | ||
+ " return (Integer) invocation.getArguments()[0];\n" | ||
+ " }};", | ||
"", | ||
"", | ||
"", | ||
"", | ||
"", | ||
" boolean b1 = someWrapper(when(true).thenCallRealMethod());", | ||
" boolean b2 = someWrapper(when(experimentation.isToggleDisabled(\"OTHER_FLAG\")).thenCallRealMethod());", | ||
"", | ||
" someWhenWrapper(when(experimentation.isToggleDisabled(\"OTHER_FLAG\"))).thenCallRealMethod();", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).thenReturn(false);", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).thenThrow(new RuntimeException());", | ||
" when(experimentation.isToggleDisabled(\"OTHER_FLAG\")).thenCallRealMethod();", | ||
" when(experimentation.isToggleEnabled(\"OTHER_FLAG\")).then(ans);", | ||
" when(foobar()).thenReturn(false);", | ||
" when(foobar()).thenAnswer(ans);", | ||
"// Easymock Test scenarios", | ||
"", | ||
"", | ||
"", | ||
"", | ||
"", | ||
"", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"OTHER_FLAG\")).andReturn(true).anyTimes();", | ||
" EasyMock.expect(foobar()).andReturn(true).times(5);", | ||
" EasyMock.expect(experimentation.isToggleEnabled(\"OTHER_FLAG\")).andReturn(true).times(5, 6);", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).once();", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).atLeastOnce();", | ||
" EasyMock.expect(experimentation.isToggleDisabled(\"OTHER_FLAG\")).andReturn(true).anyTimes();", | ||
"// JUnit Assert Test scearios", | ||
"", | ||
"", | ||
" assertFalse(experimentation.isToggleEnabled(\"OTHER_FLAG\"));", | ||
" assertTrue(experimentation.isToggleDisabled(\"OTHER_FLAG\"));", | ||
" assertTrue(foobar());", | ||
" }", | ||
" public boolean foobar() { return true;}", | ||
" public boolean someWrapper(Object obj) { return true;}", | ||
" public OngoingStubbing<Boolean> someWhenWrapper(OngoingStubbing<Boolean> x) { return x;}", | ||
"}") | ||
.doTest(); | ||
} | ||
|
||
@Test | ||
public void testMethodChainTreated() { | ||
String staleFlag = "stale_flag"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is matching on every lambda expression of the original AST, in parallel with the traversal you are using to rewrite XP flag using code. This will result in two issues:
a) This will not actually simplify expressions after Piranha removes the code related to stale flags, since that happens later in the traversal (deeper inside the AST) than this matcher. And also since Piranha doesn't rewrite the AST in place (for good reasons, given the EP model) but rather generates a fix suggestion to be applied after compilation and analysis by the checker.
b) This will switch all single-statement lambdas present in the original code to use the expression body style vs the block body style. This is not something we want Piranha to be doing. In general, this is not the tool to do code changes that are entirely unrelated to removing stale flags.
I am not sure that the refactoring you want to implement can be done easily. You would either need to match the "lambda containing an if" pattern here and duplicate/refactor much of the
matchIf
logic, or have secondary traversal over the already rewritten code, but this is hard to do inside the Piranha checker itself and would require some integration work to run two rewriters (it would also need some logic to detect that the code being "simplified" was already touched by Piranha and is not an unrelated lambda expression).This is an instance of the "deep clean" issue with Piranha Java.