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 +} 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