From a778e235c5c62982e69e9386fa8a0f265d5be646 Mon Sep 17 00:00:00 2001 From: Mitch Ware Date: Sat, 6 Apr 2024 11:06:44 -0400 Subject: [PATCH 1/2] Allow consumers to supply extra denylisted entries via ServiceLoader --- .../denylistedapis/DenyListedApiDetector.kt | 19 ++++++++++++++++--- .../denylistedapis/DenyListedEntryLoader.kt | 5 +++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedEntryLoader.kt diff --git a/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedApiDetector.kt b/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedApiDetector.kt index fb0c0ac1..d098225d 100644 --- a/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedApiDetector.kt +++ b/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedApiDetector.kt @@ -28,6 +28,7 @@ import com.android.tools.lint.detector.api.XmlScanner import com.intellij.psi.PsiField import com.intellij.psi.PsiMethod import java.util.EnumSet +import java.util.ServiceLoader import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.UElement import org.jetbrains.uast.UExpression @@ -54,7 +55,7 @@ internal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner override fun visitElement(context: XmlContext, element: Element) = CONFIG.visitor(context, element) - private class DenyListConfig(vararg entries: DenyListedEntry) { + private class DenyListConfig(entries: Set) { private class TypeConfig(entries: List) { @Suppress("UNCHECKED_CAST") // Safe because of filter call. val functionEntries = @@ -213,8 +214,8 @@ internal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner val DEFAULT_ISSUE = createIssue("DenyListedApi") val BLOCKING_ISSUE = createIssue("DenyListedBlockingApi") - private val CONFIG = - DenyListConfig( + private val ENTRIES = + setOf( DenyListedEntry( className = "io.reactivex.rxjava3.core.Observable", functionName = "hide", @@ -466,6 +467,10 @@ internal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner *rxJavaBlockingCalls().toTypedArray(), ) + private val EXTERNAL_ENTRIES = loadExternalEntries() + + private val CONFIG = DenyListConfig(ENTRIES + EXTERNAL_ENTRIES) + val ISSUES = CONFIG.issues private fun createIssue( @@ -491,6 +496,14 @@ internal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner ), ) } + + private fun loadExternalEntries(): Set { + val loader = ServiceLoader.load(DenyListedEntryLoader::class.java) + + return mutableSetOf().apply { + loader.iterator().forEachRemaining { addAll(it.entries) } + } + } } } diff --git a/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedEntryLoader.kt b/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedEntryLoader.kt new file mode 100644 index 00000000..9dacf202 --- /dev/null +++ b/slack-lint-checks/src/main/java/slack/lint/denylistedapis/DenyListedEntryLoader.kt @@ -0,0 +1,5 @@ +package slack.lint.denylistedapis + +interface DenyListedEntryLoader { + val entries: Set +} From 879cd827f39ed3195ab9f6571e0722cfc592458b Mon Sep 17 00:00:00 2001 From: Mitch Ware Date: Sat, 6 Apr 2024 11:28:27 -0400 Subject: [PATCH 2/2] Add test case for externally-loaded denylist entries --- .../DenyListedApiDetectorTest.kt | 43 +++++++++++++++++++ .../lint/denylistedapis/TestEntriesLoader.kt | 12 ++++++ ....lint.denylistedapis.DenyListedEntryLoader | 1 + 3 files changed, 56 insertions(+) create mode 100644 slack-lint-checks/src/test/java/slack/lint/denylistedapis/TestEntriesLoader.kt create mode 100644 slack-lint-checks/src/test/resources/META-INF/services/slack.lint.denylistedapis.DenyListedEntryLoader diff --git a/slack-lint-checks/src/test/java/slack/lint/denylistedapis/DenyListedApiDetectorTest.kt b/slack-lint-checks/src/test/java/slack/lint/denylistedapis/DenyListedApiDetectorTest.kt index 0647b879..7b52fe7e 100644 --- a/slack-lint-checks/src/test/java/slack/lint/denylistedapis/DenyListedApiDetectorTest.kt +++ b/slack-lint-checks/src/test/java/slack/lint/denylistedapis/DenyListedApiDetectorTest.kt @@ -700,6 +700,38 @@ class DenyListedApiDetectorTest : BaseSlackLintTest() { ) } + @Test + fun externalDenylistEntries() { + lint() + .files( + EXTERNAL_ENTRY_TESTCLASS_STUB, + kotlin( + """ + package foo + + import slack.lint.TestClass + + class SomeClass { + fun test() { + TestClass.run() + } + } + """ + ) + .indented() + ) + .run() + .expect( + """ + src/foo/SomeClass.kt:7: Error: TestClass.run() is disallowed in tests via external denylist entry. [DenyListedApi] + TestClass.run() + ~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + companion object { private val FLOWABLE_STUB = java( @@ -1026,5 +1058,16 @@ class DenyListedApiDetectorTest : BaseSlackLintTest() { """ ) .indented() + + private val EXTERNAL_ENTRY_TESTCLASS_STUB = + kotlin( + """ + package slack.lint + + object TestClass { + fun run() {} + } + """.trimIndent() + ) } } diff --git a/slack-lint-checks/src/test/java/slack/lint/denylistedapis/TestEntriesLoader.kt b/slack-lint-checks/src/test/java/slack/lint/denylistedapis/TestEntriesLoader.kt new file mode 100644 index 00000000..1ebdb902 --- /dev/null +++ b/slack-lint-checks/src/test/java/slack/lint/denylistedapis/TestEntriesLoader.kt @@ -0,0 +1,12 @@ +package slack.lint.denylistedapis + +internal class TestEntriesLoader: DenyListedEntryLoader { + override val entries: Set = + setOf( + DenyListedEntry( + className = "slack.lint.TestClass", + functionName = "run", + errorMessage = "TestClass.run() is disallowed in tests via external denylist entry." + ) + ) +} \ No newline at end of file diff --git a/slack-lint-checks/src/test/resources/META-INF/services/slack.lint.denylistedapis.DenyListedEntryLoader b/slack-lint-checks/src/test/resources/META-INF/services/slack.lint.denylistedapis.DenyListedEntryLoader new file mode 100644 index 00000000..9bf5de9e --- /dev/null +++ b/slack-lint-checks/src/test/resources/META-INF/services/slack.lint.denylistedapis.DenyListedEntryLoader @@ -0,0 +1 @@ +slack.lint.denylistedapis.TestEntriesLoader \ No newline at end of file