Skip to content

Commit

Permalink
[improvement] Ensure Optional#orElse argument is not method invocation (
Browse files Browse the repository at this point in the history
  • Loading branch information
gatesn authored Jun 21, 2019
1 parent 2c1d717 commit f1264dd
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.palantir.baseline.errorprone;

import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.matchers.method.MethodMatchers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;

@AutoService(BugChecker.class)
@BugPattern(
name = "OptionalOrElseMethodInvocation",
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks",
linkType = BugPattern.LinkType.CUSTOM,
severity = SeverityLevel.ERROR,
summary = "Expression passed to Optional#orElse invokes a method, use Optional#orElseGet instead")
public final class OptionalOrElseMethodInvocation extends BugChecker implements MethodInvocationTreeMatcher {

private static final long serialVersionUID = 1L;

private static final Matcher<ExpressionTree> OR_ELSE_METHOD = MethodMatchers.instanceMethod()
.onExactClass("java.util.Optional")
.named("orElse");

private static final Matcher<Tree> METHOD_INVOCATIONS =
Matchers.contains(ExpressionTree.class, MethodMatchers.anyMethod());

@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!OR_ELSE_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}

ExpressionTree orElseArg = tree.getArguments().get(0);

if (!METHOD_INVOCATIONS.matches(orElseArg, state)) {
return Description.NO_MATCH;
}

return buildDescription(tree)
.setMessage("Expression passed to Optional#orElse invokes a method")
.addFix(SuggestedFix.builder()
.postfixWith(tree.getMethodSelect(), "Get")
.prefixWith(orElseArg, "() -> ")
.build())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.palantir.baseline.errorprone;

import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Before;
import org.junit.Test;

public final class OptionalOrElseMethodInvocationTests {

private CompilationTestHelper compilationHelper;
private BugCheckerRefactoringTestHelper refactoringTestHelper;

@Before
public void before() {
compilationHelper = CompilationTestHelper.newInstance(OptionalOrElseMethodInvocation.class, getClass());
refactoringTestHelper = BugCheckerRefactoringTestHelper.newInstance(
new OptionalOrElseMethodInvocation(), getClass());
}

private void test(String expr) {
compilationHelper
.addSourceLines(
"Test.java",
"import java.util.Optional;",
"class Test {",
" String f() { return \"hello\"; }",
" String s = \"world\";",
" // BUG: Diagnostic contains: invokes a method",
" private final String string = Optional.of(\"hello\").orElse(" + expr + ");",
"}")
.doTest();
}

@Test
public void testNonCompileTimeConstantExpression() {
test("f()");
test("s + s");
test("\"world\" + s");
test("\"world\".substring(1)");
}

@Test
public void testNonCompileTimeConstantExpression_replacement() {
refactoringTestHelper
.addInputLines(
"Test.java",
"import java.util.Optional;",
"class Test {",
" String f() { return \"hello\"; }",
" private final String string = Optional.of(\"hello\").orElse(f());",
"}")
.addOutputLines(
"Test.java",
"import java.util.Optional;",
"class Test {",
" String f() { return \"hello\"; }",
" private final String string = Optional.of(\"hello\").orElseGet(() -> f());",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

@Test
public void negative() {
compilationHelper
.addSourceLines(
"Test.java",
"import java.util.Optional;",
"class Test {",
" private static final String compileTimeConstant = \"constant\";",
" String f() { return \"hello\"; }",
" void test() {",
" Optional.of(\"hello\").orElse(\"constant\");",
" Optional.of(\"hello\").orElse(compileTimeConstant);",
" String string = f();",
" Optional.of(\"hello\").orElse(string);",
" Optional.of(\"hello\").orElseGet(() -> f());",
" }",
"}")
.doTest();
}

}

0 comments on commit f1264dd

Please sign in to comment.