Skip to content
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

fix #894: Error Prone UnnecessaryLambdaArgumentParentheses #1186

Merged
merged 5 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c
- `BracesRequired`: Require braces for loops and if expressions.
- `CollectionStreamForEach`: Collection.forEach is more efficient than Collection.stream().forEach.
- `LoggerEnclosingClass`: Loggers created using getLogger(Class<?>) must reference their enclosing class.
- `UnnecessaryLambdaArgumentParentheses`: Lambdas with a single parameter do not require argument parentheses.

### Programmatic Application

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* (c) Copyright 2020 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.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ErrorProneToken;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.tools.javac.parser.Tokens;
import com.sun.tools.javac.tree.JCTree;
import java.util.List;

/**
* UnnecessaryLambdaArgumentParentheses provides similar functionality to the upstream UnnecessaryParentheses, but
* specifically for single-parameter lambda arguments which are not covered by the existing check. Perhaps this can be
* contributed upstream. There's an argument against combining the two because parentheses around lambda arguments
* cannot be parsed directly from the AST where other parenthesis checked by UnnecessaryParentheses can.
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "UnnecessaryLambdaArgumentParentheses",
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks",
linkType = BugPattern.LinkType.CUSTOM,
providesFix = BugPattern.ProvidesFix.REQUIRES_HUMAN_ATTENTION,
severity = BugPattern.SeverityLevel.WARNING,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should go with error since checkstyle previous enforced this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most projects fail on WARNING -- iirc we tried using ERROR for the BracesRequired check but ran into issues (possibly with generated code).

Since excavator will auto-fix anything in projects that don't fail on warnings, I don't think there's much risk.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok sure!

summary = "Lambdas with a single parameter do not require argument parentheses.")
public final class UnnecessaryLambdaArgumentParentheses extends BugChecker
implements BugChecker.LambdaExpressionTreeMatcher {

@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
if (!quickCheck(tree, state)) {
return Description.NO_MATCH;
}
// Avoid using getOffsetTokensForNode at this point because it's significantly more expensive
List<ErrorProneToken> tokens = state.getTokensForNode(tree);
int depth = 0;
int identifiers = 0;
for (int i = 0; i < tokens.size(); i++) {
ErrorProneToken token = tokens.get(i);
// parameters with types require parens
if (token.kind() == Tokens.TokenKind.IDENTIFIER && ++identifiers > 1) {
return Description.NO_MATCH;
} else if (token.kind() == Tokens.TokenKind.ARROW) {
return Description.NO_MATCH;
} else if (token.kind() == Tokens.TokenKind.LPAREN) {
depth++;
} else if (token.kind() == Tokens.TokenKind.RPAREN && --depth == 0) {
List<ErrorProneToken> offsetTokens = state.getOffsetTokensForNode(tree);
ErrorProneToken firstToken = offsetTokens.get(0);
ErrorProneToken offsetToken = offsetTokens.get(i);
return buildDescription(tree.getParameters().get(0))
.addFix(SuggestedFix.builder()
.replace(firstToken.pos(), firstToken.endPos(), "")
.replace(offsetToken.pos(), offsetToken.endPos(), "")
.build())
.build();
}
}
return Description.NO_MATCH;
}

// Fast check to rule out lambdas that don't violate this check without tokenizing the source.
private static boolean quickCheck(LambdaExpressionTree tree, VisitorState state) {
if (tree.getParameters().size() != 1) {
return false;
}
int start = ((JCTree) tree).getStartPosition();
if (start == -1) {
return false;
}
CharSequence source = state.getSourceCode();
if (source == null) {
return false;
}
// Fast check to avoid tokenizing all lambdas unnecessarily.
return source.charAt(start) == '(';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* (c) Copyright 2020 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 java.util.function.Predicate;
import org.junit.jupiter.api.Test;

class UnnecessaryLambdaArgumentParenthesesTest {

@Test
public void testFix() {
fix().addInputLines(
"Test.java",
"import " + Predicate.class.getName() + ';',
"class Test {",
" Predicate<Object> a = (value) -> value == null;",
" Predicate<Object> b = ( value ) -> value == null;",
" Predicate<Object> c = /* (value) -> value*/(value) -> value == null;",
" Predicate<Object> d = (value) /*(value) -> value*/ -> value == null;",
"}")
.addOutputLines(
"Test.java",
"import " + Predicate.class.getName() + ';',
"class Test {",
" Predicate<Object> a = value -> value == null;",
" Predicate<Object> b = value -> value == null;",
" Predicate<Object> c = /* (value) -> value*/value -> value == null;",
" Predicate<Object> d = value /*(value) -> value*/ -> value == null;",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

@Test
public void testNegative() {
fix().addInputLines(
"Test.java",
"import " + Predicate.class.getName() + ';',
"class Test {",
" Predicate<Object> a = value -> value == null;",
" Predicate<Object> b = value -> value == null;",
" Predicate<Object> c = /* (value) -> value*/value -> value == null;",
" Predicate<Object> d = value /*(value) -> value*/ -> value == null;",
" Predicate<?> e = (String value) -> value == null;",
"}")
.expectUnchanged()
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

private RefactoringValidator fix() {
return RefactoringValidator.of(new UnnecessaryLambdaArgumentParentheses(), getClass());
}
}
8 changes: 8 additions & 0 deletions changelog/@unreleased/pr-1186.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type: improvement
improvement:
description:
Replace the checkstyle UnnecessaryParentheses check with error-prone. The existing upstream
UnnecessaryParentheses check covers most cases, a new UnnecessaryLambdaArgumentParentheses
check covers the rest.
links:
- https://github.com/palantir/gradle-baseline/pull/1186
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@
<message key="name.invalidPattern" value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypecastParenPad"/> <!-- Java Style Guide: Horizontal whitespace -->
<module name="UnnecessaryParentheses"/>
<module name="UnusedImports"> <!-- Java Style Guide: No unused imports -->
<property name="processJavadoc" value="true"/>
</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class BaselineErrorProneExtension {
"StrictUnusedVariable",
"StringBuilderConstantParameters",
"ThrowError",
"UnnecessaryLambdaArgumentParentheses",
// TODO(ckozak): re-enable pending scala check
// "ThrowSpecificity",
"UnsafeGaugeRegistration",
Expand Down