diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NegativeBoolean.java b/core/src/main/java/com/google/errorprone/bugpatterns/NegativeBoolean.java new file mode 100644 index 00000000000..456eeabb70e --- /dev/null +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NegativeBoolean.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * 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.google.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.util.ASTHelpers.getSymbol; + +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Symbol; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.TypeKind; + +/** Discourages the use of negative boolean names. */ +@BugPattern(summary = "Prefer positive boolean names", severity = WARNING) +public final class NegativeBoolean extends BugChecker implements VariableTreeMatcher { + + // Match names beginning with 'no' or 'not' + private static final Pattern NEGATIVE_NAME = Pattern.compile("^not?[A-Z].*$"); + + @Override + public Description matchVariable(VariableTree node, VisitorState state) { + // Only consider local variables of type boolean + Symbol symbol = getSymbol(node); + if (!symbol.getKind().equals(ElementKind.LOCAL_VARIABLE)) { + return Description.NO_MATCH; + } + if (!symbol.asType().getKind().equals(TypeKind.BOOLEAN)) { + return Description.NO_MATCH; + } + + if (isNegativeName(symbol.getSimpleName().toString())) { + return describeMatch(node); + } + return Description.NO_MATCH; + } + + private static boolean isNegativeName(String name) { + Matcher m = NEGATIVE_NAME.matcher(name); + return m.matches(); + } +} diff --git a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java index 27abf659581..e07059e67ef 100644 --- a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java +++ b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java @@ -271,6 +271,7 @@ import com.google.errorprone.bugpatterns.NamedLikeContextualKeyword; import com.google.errorprone.bugpatterns.NarrowCalculation; import com.google.errorprone.bugpatterns.NarrowingCompoundAssignment; +import com.google.errorprone.bugpatterns.NegativeBoolean; import com.google.errorprone.bugpatterns.NegativeCharLiteral; import com.google.errorprone.bugpatterns.NestedInstanceOfConditions; import com.google.errorprone.bugpatterns.NewFileSystem; @@ -1216,6 +1217,7 @@ public static ScannerSupplier warningChecks() { MultiVariableDeclaration.class, MultipleTopLevelClasses.class, MutableGuiceModule.class, + NegativeBoolean.class, NoAllocationChecker.class, NonCanonicalStaticMemberImport.class, NonFinalStaticField.class, // Intentionally disabled in OSS. diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/NegativeBooleanTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/NegativeBooleanTest.java new file mode 100644 index 00000000000..fa5725f0995 --- /dev/null +++ b/core/src/test/java/com/google/errorprone/bugpatterns/NegativeBooleanTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * 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.google.errorprone.bugpatterns; + +import com.google.errorprone.CompilationTestHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class NegativeBooleanTest { + + private final CompilationTestHelper testHelper = + CompilationTestHelper.newInstance(NegativeBoolean.class, getClass()); + + @Test + public void prefix_no_isFlagged() { + testHelper + .addSourceLines( + "Test.java", + """ + class Test { + void foo() { + // BUG: Diagnostic contains: Prefer positive + boolean noBar; + } + } + """) + .doTest(); + } + + @Test + public void prefix_not_isFlagged() { + testHelper + .addSourceLines( + "Test.java", + """ + class Test { + void foo() { + // BUG: Diagnostic contains: Prefer positive + boolean notBar; + } + } + """) + .doTest(); + } + + @Test + public void prefix_notable_isNotFlagged() { + testHelper + .addSourceLines( + "Test.java", + """ + class Test { + void foo() { + boolean notableBar; + } + } + """) + .doTest(); + } + + @Test + public void integer_isNotFlagged() { + testHelper + .addSourceLines( + "Test.java", + """ + class Test { + void foo() { + int notBar; + } + } + """) + .doTest(); + } + + @Test + public void parameter_isNotFlagged() { + testHelper + .addSourceLines( + "Test.java", + """ + class Test { + void foo(boolean notBar) { + return; + } + } + """) + .doTest(); + } +}