diff --git a/anvil/anvil-compiler/src/main/java/com/duckduckgo/anvil/compiler/ContributesActivePluginPointCodeGenerator.kt b/anvil/anvil-compiler/src/main/java/com/duckduckgo/anvil/compiler/ContributesActivePluginPointCodeGenerator.kt index 07c7b7874246..a600240933ca 100644 --- a/anvil/anvil-compiler/src/main/java/com/duckduckgo/anvil/compiler/ContributesActivePluginPointCodeGenerator.kt +++ b/anvil/anvil-compiler/src/main/java/com/duckduckgo/anvil/compiler/ContributesActivePluginPointCodeGenerator.kt @@ -49,6 +49,7 @@ import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import dagger.Binds import java.io.File +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.name.FqName @@ -123,6 +124,16 @@ class ContributesActivePluginPointCodeGenerator : CodeGenerator { val pluginClassType = vmClass.pluginClassName(ContributesActivePluginPoint::class.fqName) ?: vmClass.asClassName() val featureName = "pluginPoint${pluginClassType.simpleName}" + // Check if there's another plugin point class that has the same class simplename + // we can't allow that because the backing remote feature would be the same + val existingFeature = featureBackedClassNames.putIfAbsent(featureName, vmClass.fqName) + if (existingFeature != null) { + throw AnvilCompilationException( + "${vmClass.fqName} plugin point naming is duplicated, previous found in $existingFeature", + element = vmClass.clazz.identifyingElement, + ) + } + val content = FileSpec.buildFile(generatedPackage, pluginPointClassFileName) { // This is the normal plugin point addType( @@ -304,6 +315,16 @@ class ContributesActivePluginPointCodeGenerator : CodeGenerator { val pluginRemoteFeatureStoreClassName = "${vmClass.shortName}_ActivePlugin_RemoteFeature_MultiProcessStore" val pluginPriority = vmClass.annotations.firstOrNull { it.fqName == ContributesActivePlugin::class.fqName }?.priorityOrNull() + // Check if there's another plugin class, in the same plugin point, that has the same class simplename + // we can't allow that because the backing remote feature would be the same + val existingFeature = featureBackedClassNames.putIfAbsent("${featureName}_$parentFeatureName", vmClass.fqName) + if (existingFeature != null) { + throw AnvilCompilationException( + "${vmClass.fqName} plugin name is duplicated, previous found in $existingFeature", + element = vmClass.clazz.identifyingElement, + ) + } + val content = FileSpec.buildFile(generatedPackage, pluginClassName) { // First create the class that will contribute the active plugin. // We do expect that the plugins are define using the "ContributesActivePlugin" annotation but are also injected @@ -570,6 +591,8 @@ class ContributesActivePluginPointCodeGenerator : CodeGenerator { } companion object { + internal val featureBackedClassNames = ConcurrentHashMap() + private val pluginPointFqName = FqName("com.duckduckgo.common.utils.plugins.PluginPoint") private val dispatcherProviderFqName = FqName("com.duckduckgo.common.utils.DispatcherProvider") private val activePluginPointFqName = FqName("com.duckduckgo.common.utils.plugins.InternalActivePluginPoint") diff --git a/lint-rules/src/main/java/com/duckduckgo/lint/WrongPluginPointCollectorDetector.kt b/lint-rules/src/main/java/com/duckduckgo/lint/WrongPluginPointCollectorDetector.kt index f60e7af6f4b2..0e54c402d9c4 100644 --- a/lint-rules/src/main/java/com/duckduckgo/lint/WrongPluginPointCollectorDetector.kt +++ b/lint-rules/src/main/java/com/duckduckgo/lint/WrongPluginPointCollectorDetector.kt @@ -84,7 +84,7 @@ class WrongPluginPointCollectorDetector : Detector(), SourceCodeScanner { } private fun PsiClass.isActivePlugin(): Boolean { - return this.isSubtypeOf("com.duckduckgo.common.utils.plugins.ActivePluginPoint.ActivePlugin") + return this.isSubtypeOf("com.duckduckgo.common.utils.plugins.ActivePlugin") } private fun handleField(node: UField) { node.type.let { psiType -> @@ -95,7 +95,7 @@ class WrongPluginPointCollectorDetector : Detector(), SourceCodeScanner { for (typeArgument in typeArguments) { val typeArgumentClass = (typeArgument as? PsiClassType)?.resolve() if (typeArgumentClass?.isSubtypeOf( - "com.duckduckgo.common.utils.plugins.ActivePluginPoint.ActivePlugin" + "com.duckduckgo.common.utils.plugins.ActivePlugin" ) == true) { context.reportError(node, WRONG_PLUGIN_POINT_ISSUE) } diff --git a/lint-rules/src/test/java/com/duckduckgo/lint/WrongPluginPointCollectorDetectorTest.kt b/lint-rules/src/test/java/com/duckduckgo/lint/WrongPluginPointCollectorDetectorTest.kt index dbed099b65ea..4e4bfd97287e 100644 --- a/lint-rules/src/test/java/com/duckduckgo/lint/WrongPluginPointCollectorDetectorTest.kt +++ b/lint-rules/src/test/java/com/duckduckgo/lint/WrongPluginPointCollectorDetectorTest.kt @@ -19,40 +19,40 @@ package com.duckduckgo.lint import com.android.tools.lint.checks.infrastructure.TestFiles.kt import com.android.tools.lint.checks.infrastructure.TestLintTask.lint import com.duckduckgo.lint.WrongPluginPointCollectorDetector.Companion.WRONG_PLUGIN_POINT_ISSUE +import com.duckduckgo.lint.utils.PLUGIN_POINT_ANNOTATIONS_API +import com.duckduckgo.lint.utils.PLUGIN_POINT_API import org.junit.Test class WrongPluginPointCollectorDetectorTest { @Test fun `test normal plugin point constructor parameter collecting active plugins`() { lint() - .files(kt(""" - package com.duckduckgo.common.utils.plugins - - interface PluginPoint { - fun getPlugins(): Collection - } - - interface ActivePluginPoint { - interface ActivePlugin { - suspend fun isActive(): Boolean = true - } - } - - interface MyPlugin - interface MyPluginActivePlugin : ActivePluginPoint.ActivePlugin + .files( + PLUGIN_POINT_API, + PLUGIN_POINT_ANNOTATIONS_API, + kt(""" + package com.test.plugins - class Duck(private val pp: PluginPoint) { - fun quack() { + import com.duckduckgo.common.utils.plugins.ActivePlugin + import com.duckduckgo.common.utils.plugins.PluginPoint + import com.duckduckgo.anvil.annotations.ContributesActivePlugin + + interface MyPlugin + interface MyPluginActivePlugin : ActivePlugin + + class Duck(private val pp: PluginPoint) { + fun quack() { + } } - } - """).indented()) + """).indented() + ) .issues(WRONG_PLUGIN_POINT_ISSUE) .run() .expect(""" - src/com/duckduckgo/common/utils/plugins/PluginPoint.kt:16: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] + src/com/test/plugins/MyPlugin.kt:10: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] class Duck(private val pp: PluginPoint) { ~~~~ - src/com/duckduckgo/common/utils/plugins/PluginPoint.kt:16: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] + src/com/test/plugins/MyPlugin.kt:10: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] class Duck(private val pp: PluginPoint) { ~~ 2 errors, 0 warnings @@ -62,26 +62,23 @@ class WrongPluginPointCollectorDetectorTest { @Test fun `test active plugin point constructor parameter collecting active plugins`() { lint() - .files(kt(""" - package com.duckduckgo.common.utils.plugins - - interface PluginPoint { - fun getPlugins(): Collection - } - - interface ActivePluginPoint { - interface ActivePlugin { - suspend fun isActive(): Boolean = true - } - } - - interface MyPlugin - interface MyPluginActivePlugin : ActivePluginPoint.ActivePlugin + .files( + PLUGIN_POINT_API, + PLUGIN_POINT_ANNOTATIONS_API, + kt(""" + package com.test.plugins + + import com.duckduckgo.common.utils.plugins.ActivePlugin + import com.duckduckgo.common.utils.plugins.PluginPoint + import com.duckduckgo.anvil.annotations.ContributesActivePlugin - class Duck(private val pp: PluginPoint) { - fun quack() { + interface MyPlugin + interface MyPluginActivePlugin : ActivePlugin + + class Duck(private val pp: PluginPoint) { + fun quack() { + } } - } """).indented()) .issues(WRONG_PLUGIN_POINT_ISSUE) .run() @@ -91,33 +88,30 @@ class WrongPluginPointCollectorDetectorTest { @Test fun `test normal plugin point field collecting active plugins`() { lint() - .files(kt(""" - package com.duckduckgo.common.utils.plugins - - interface PluginPoint { - fun getPlugins(): Collection - } - - interface ActivePluginPoint { - interface ActivePlugin { - suspend fun isActive(): Boolean = true - } - } + .files( + PLUGIN_POINT_API, + PLUGIN_POINT_ANNOTATIONS_API, + kt(""" + package com.test.plugins - interface MyPlugin - interface MyPluginActivePlugin : ActivePluginPoint.ActivePlugin + import com.duckduckgo.common.utils.plugins.ActivePlugin + import com.duckduckgo.common.utils.plugins.PluginPoint + import com.duckduckgo.anvil.annotations.ContributesActivePlugin - class Duck { - private val pp: PluginPoint - - fun quack() { + interface MyPlugin + interface MyPluginActivePlugin : ActivePlugin + + class Duck { + private val pp: PluginPoint + + fun quack() { + } } - } """).indented()) .issues(WRONG_PLUGIN_POINT_ISSUE) .run() .expect(""" - src/com/duckduckgo/common/utils/plugins/PluginPoint.kt:17: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] + src/com/test/plugins/MyPlugin.kt:11: Error: PluginPoint cannot be collector of ActivePlugin(s) [WrongPluginPointCollectorDetector] private val pp: PluginPoint ~~ 1 errors, 0 warnings @@ -127,28 +121,22 @@ class WrongPluginPointCollectorDetectorTest { @Test fun `test active plugin point field collecting active plugins`() { lint() - .files(kt(""" - package com.duckduckgo.common.utils.plugins - - interface PluginPoint { - fun getPlugins(): Collection - } - - interface ActivePluginPoint { - interface ActivePlugin { - suspend fun isActive(): Boolean = true - } - } - - interface MyPlugin - interface MyPluginActivePlugin : ActivePluginPoint.ActivePlugin + .files( + PLUGIN_POINT_API, + PLUGIN_POINT_ANNOTATIONS_API, + kt(""" + package com.test.plugins - class Duck { - private val pp: PluginPoint - - fun quack() { + import com.duckduckgo.common.utils.plugins.ActivePlugin + import com.duckduckgo.common.utils.plugins.PluginPoint + import com.duckduckgo.anvil.annotations.ContributesActivePlugin + + class Duck { + private val pp: PluginPoint + + fun quack() { + } } - } """).indented()) .issues(WRONG_PLUGIN_POINT_ISSUE) .run() diff --git a/lint-rules/src/test/java/com/duckduckgo/lint/utils/PluginUtils.kt b/lint-rules/src/test/java/com/duckduckgo/lint/utils/PluginUtils.kt new file mode 100644 index 000000000000..13ad9fa3f9de --- /dev/null +++ b/lint-rules/src/test/java/com/duckduckgo/lint/utils/PluginUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * 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.duckduckgo.lint.utils + +import com.android.tools.lint.checks.infrastructure.TestFiles + +internal val PLUGIN_POINT_API = TestFiles.kt( + """ + package com.duckduckgo.common.utils.plugins + + interface PluginPoint { + fun getPlugins(): Collection + } + interface InternalActivePluginPoint { + suspend fun getPlugins(): Collection + } + interface ActivePlugin { + suspend fun isActive(): Boolean = true + } + typealias ActivePluginPoint = InternalActivePluginPoint<@JvmSuppressWildcards T> + """ +).indented() +internal val PLUGIN_POINT_ANNOTATIONS_API = TestFiles.kt( + """ + package com.duckduckgo.anvil.annotations + + annotation class ContributesActivePlugin + annotation class ContributesActivePluginPoint( + val boundType: KClass<*> = Unit::class, + ) + + """ +).indented()