Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>lint</code>.
*/
class BazaarIssueRegistry : IssueRegistry() {
override val issues = listOf(PluginsOverrideInNonFinalClassDetector.ISSUE)
override val issues = listOf(
PluginsOverrideInNonFinalClassDetector.ISSUE,
PreferNotFunctionDetector.ISSUE,
)

override val api: Int
get() = CURRENT_API
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<out UElement?>> {
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
)
)
}
}
Original file line number Diff line number Diff line change
@@ -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"""
}
}