diff --git a/rules/src/main/java/com/farsitel/bazaar/lintrules/BazaarIssueRegistry.kt b/rules/src/main/java/com/farsitel/bazaar/lintrules/BazaarIssueRegistry.kt
index e6a9777..08ba4ec 100644
--- a/rules/src/main/java/com/farsitel/bazaar/lintrules/BazaarIssueRegistry.kt
+++ b/rules/src/main/java/com/farsitel/bazaar/lintrules/BazaarIssueRegistry.kt
@@ -19,12 +19,16 @@ import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.farsitel.bazaar.lintrules.rules.PluginsOverrideInNonFinalClassDetector
+import com.farsitel.bazaar.lintrules.rules.PreferNotFunctionDetector
/*
* The list of issues that will be checked when running lint.
*/
class BazaarIssueRegistry : IssueRegistry() {
- override val issues = listOf(PluginsOverrideInNonFinalClassDetector.ISSUE)
+ override val issues = listOf(
+ PluginsOverrideInNonFinalClassDetector.ISSUE,
+ PreferNotFunctionDetector.ISSUE,
+ )
override val api: Int
get() = CURRENT_API
diff --git a/rules/src/main/java/com/farsitel/bazaar/lintrules/rules/PreferNotFunctionDetector.kt b/rules/src/main/java/com/farsitel/bazaar/lintrules/rules/PreferNotFunctionDetector.kt
new file mode 100644
index 0000000..33c554f
--- /dev/null
+++ b/rules/src/main/java/com/farsitel/bazaar/lintrules/rules/PreferNotFunctionDetector.kt
@@ -0,0 +1,62 @@
+package com.farsitel.bazaar.lintrules.rules
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Detector.UastScanner
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.isKotlin
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UPrefixExpression
+import org.jetbrains.uast.UastPrefixOperator
+
+/**
+ * Detector spotting Kotlin logical-not `!` operator; prefer `not()` instead.
+ */
+class PreferNotFunctionDetector : Detector(), UastScanner {
+
+ override fun getApplicableUastTypes(): List> {
+ return listOf(UPrefixExpression::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitPrefixExpression(node: UPrefixExpression) {
+ if (isKotlin(context.uastFile?.lang).not()) {
+ return
+ }
+ if ((node.operator == UastPrefixOperator.LOGICAL_NOT).not()) {
+ return
+ }
+ context.report(
+ ISSUE,
+ node,
+ context.getLocation(node),
+ "Do not use `!`; use `not()` instead"
+ )
+ }
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ id = "PreferNotFunction",
+ briefDescription = "Prefer not() over !",
+ explanation = """
+ Do not use the `!` logical-not operator. Use the `not()` function instead.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ PreferNotFunctionDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/rules/src/test/kotlin/com/farsitel/bazaar/lintrules/PreferNotFunctionDetectorTest.kt b/rules/src/test/kotlin/com/farsitel/bazaar/lintrules/PreferNotFunctionDetectorTest.kt
new file mode 100644
index 0000000..101748a
--- /dev/null
+++ b/rules/src/test/kotlin/com/farsitel/bazaar/lintrules/PreferNotFunctionDetectorTest.kt
@@ -0,0 +1,137 @@
+package com.farsitel.bazaar.lintrules
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintResult
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.farsitel.bazaar.lintrules.rules.PreferNotFunctionDetector
+import org.intellij.lang.annotations.Language
+import org.junit.Test
+
+class PreferNotFunctionDetectorTest {
+
+ @Test
+ fun testBangOperatorError() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(flag: Boolean): Boolean {
+ return !flag
+ }
+ """
+ )
+ .expect(EXPECTED_BANG_ERROR)
+ }
+
+ @Test
+ fun testBangInIfConditionError() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(flag: Boolean) {
+ if (!flag) {
+ return
+ }
+ }
+ """
+ )
+ .expectErrorCount(1)
+ }
+
+ @Test
+ fun testMultipleBangOperatorsError() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(a: Boolean, b: Boolean): Boolean {
+ return !a && !b
+ }
+ """
+ )
+ .expectErrorCount(2)
+ }
+
+ @Test
+ fun testNotFunctionClean() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(flag: Boolean): Boolean {
+ return flag.not()
+ }
+ """
+ )
+ .expectClean()
+ }
+
+ @Test
+ fun testNotEqualsClean() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(a: Int, b: Int): Boolean {
+ return a != b
+ }
+ """
+ )
+ .expectClean()
+ }
+
+ @Test
+ fun testNotNullAssertClean() {
+ baseTest(
+ """
+ package test.pkg
+
+ fun check(value: String?): String {
+ return value!!
+ }
+ """
+ )
+ .expectClean()
+ }
+
+ @Test
+ fun testJavaBangClean() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+
+ class Test {
+ boolean check(boolean flag) {
+ return !flag;
+ }
+ }
+ """
+ ).indented()
+ )
+ .issues(PreferNotFunctionDetector.ISSUE)
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .run()
+ .expectClean()
+ }
+
+ private fun baseTest(@Language("kotlin") source: String): TestLintResult {
+ return lint()
+ .files(kotlin("src/test/pkg/Test.kt", source).indented())
+ .issues(PreferNotFunctionDetector.ISSUE)
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .run()
+ }
+
+ companion object {
+ const val EXPECTED_BANG_ERROR = """
+src/test/pkg/Test.kt:4: Error: Do not use !; use not() instead [PreferNotFunction]
+ return !flag
+ ~~~~~
+1 errors, 0 warnings"""
+ }
+}