From f6643d0bee5f091ccd5706b4fa85831f8eedbf34 Mon Sep 17 00:00:00 2001 From: Markus Schwarz Date: Sat, 6 Aug 2022 00:26:49 +0200 Subject: [PATCH 1/2] Allow additional types in @KotlinCoreEnvironmentTest annotation --- detekt-test-utils/api/detekt-test-utils.api | 1 + .../rules/KotlinEnvironmentTestSetup.kt | 13 ++- detekt-test/build.gradle.kts | 2 + .../config/KotlinCoreEnvironmentTestSpec.kt | 94 +++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt diff --git a/detekt-test-utils/api/detekt-test-utils.api b/detekt-test-utils/api/detekt-test-utils.api index 6e61e268505..c102c1d78a6 100644 --- a/detekt-test-utils/api/detekt-test-utils.api +++ b/detekt-test-utils/api/detekt-test-utils.api @@ -48,6 +48,7 @@ public final class io/github/detekt/test/utils/StringPrintStream : java/io/Print public abstract interface annotation class io/gitlab/arturbosch/detekt/rules/KotlinCoreEnvironmentTest : java/lang/annotation/Annotation { public abstract fun additionalJavaSourcePaths ()[Ljava/lang/String; + public abstract fun additionalTypes ()[Ljava/lang/Class; } public final class io/gitlab/arturbosch/detekt/rules/KotlinEnvironmentTestSetupKt { diff --git a/detekt-test-utils/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinEnvironmentTestSetup.kt b/detekt-test-utils/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinEnvironmentTestSetup.kt index 97f59ef5e36..4dc1f41a008 100644 --- a/detekt-test-utils/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinEnvironmentTestSetup.kt +++ b/detekt-test-utils/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinEnvironmentTestSetup.kt @@ -12,6 +12,7 @@ import org.spekframework.spek2.dsl.Root import org.spekframework.spek2.lifecycle.CachingMode import java.io.File import java.nio.file.Path +import kotlin.reflect.KClass @Deprecated( "This is specific to Spek and will be removed in a future release. Documentation has been updated to " + @@ -33,6 +34,7 @@ fun Root.setupKotlinEnvironment(additionalJavaSourceRootPath: Path? = null) { @Target(AnnotationTarget.CLASS) @ExtendWith(KotlinEnvironmentResolver::class) annotation class KotlinCoreEnvironmentTest( + val additionalTypes: Array> = [], val additionalJavaSourcePaths: Array = [] ) @@ -48,7 +50,10 @@ internal class KotlinEnvironmentResolver : ParameterResolver { override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { val closeableWrapper = extensionContext.wrapper ?: CloseableWrapper( - createEnvironment(additionalJavaSourceRootPaths = extensionContext.additionalJavaSourcePaths()) + createEnvironment( + additionalJavaSourceRootPaths = extensionContext.additionalJavaSourcePaths(), + additionalRootPaths = extensionContext.additionalRootPaths().toList() + ) ).also { extensionContext.wrapper = it } return closeableWrapper.wrapper.env } @@ -61,6 +66,12 @@ internal class KotlinEnvironmentResolver : ParameterResolver { .find { it is KotlinCoreEnvironmentTest } as? KotlinCoreEnvironmentTest ?: return emptyList() return annotation.additionalJavaSourcePaths.map { resourceAsPath(it).toFile() } } + + private fun ExtensionContext.additionalRootPaths(): Set { + val annotation = requiredTestClass.annotations + .find { it is KotlinCoreEnvironmentTest } as? KotlinCoreEnvironmentTest ?: return emptySet() + return annotation.additionalTypes.map { File(it.java.protectionDomain.codeSource.location.path) }.toSet() + } } private class CloseableWrapper(val wrapper: KotlinCoreEnvironmentWrapper) : diff --git a/detekt-test/build.gradle.kts b/detekt-test/build.gradle.kts index 1740fae4ac0..c4454e0ed62 100644 --- a/detekt-test/build.gradle.kts +++ b/detekt-test/build.gradle.kts @@ -9,6 +9,8 @@ dependencies { implementation(projects.detektUtils) compileOnly(libs.assertj) implementation(projects.detektCore) + + testImplementation(libs.assertj) } tasks.apiDump { diff --git a/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt b/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt new file mode 100644 index 00000000000..9e82bf1421f --- /dev/null +++ b/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt @@ -0,0 +1,94 @@ +package io.gitlab.arturbosch.detekt.core.config + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution +import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest +import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +internal class KotlinCoreEnvironmentTestSpec { + + private val code = """ + import io.gitlab.arturbosch.detekt.api.Config + import io.gitlab.arturbosch.detekt.api.Rule + + class B(config: Config) : Rule(config) { + override val issue = error("I don't care") + } + """.trimIndent() + + private val rule = VerifyTypeHierarchieAvailable() + + @Nested + @KotlinCoreEnvironmentTest + inner class `Without additional types`(private val env: KotlinCoreEnvironment) { + @Test + fun `no types from detekt api are available on classpath`() { + val actual = rule.compileAndLintWithContext(env, code).single() + + assertThat(actual.message).isEqualTo("[]") + } + } + + @Nested + inner class `With additional types` { + val expected = "[" + + "io.gitlab.arturbosch.detekt.api.Rule, " + + "io.gitlab.arturbosch.detekt.api.BaseRule, " + + "io.gitlab.arturbosch.detekt.api.DetektVisitor, " + + "org.jetbrains.kotlin.psi.KtTreeVisitorVoid" + + "]" + + @Nested + @KotlinCoreEnvironmentTest(additionalTypes = [Rule::class]) + inner class `Only addiontial types`(private val env: KotlinCoreEnvironment) { + @Test + fun `types from detekt api are available on classpath`() { + val actual = rule.compileAndLintWithContext(env, code).single() + + assertThat(actual.message).isEqualTo(expected) + } + } + + @Nested + @KotlinCoreEnvironmentTest(additionalTypes = [Rule::class, CharRange::class]) + inner class `Also types that are already available`(private val env: KotlinCoreEnvironment) { + @Test + fun `no conflict if types are added multiple times`() { + val actual = rule.compileAndLintWithContext(env, code).single() + + assertThat(actual.message).isEqualTo(expected) + } + } + } + + @RequiresTypeResolution + private class VerifyTypeHierarchieAvailable : Rule() { + override val issue: Issue = Issue("a", Severity.Minor, "", Debt.FIVE_MINS) + + override fun visitClass(klass: KtClass) { + super.visitClass(klass) + + val superTypes = bindingContext[BindingContext.CLASS, klass] + ?.getAllSuperclassesWithoutAny() + ?.map { checkNotNull(it.fqNameOrNull()).toString() } + .orEmpty() + + report( + CodeSmell(issue, Entity.atName(klass), superTypes.toString()) + ) + } + } +} From 0b0789664a307cced4c1dda1be58ff841e2ee56e Mon Sep 17 00:00:00 2001 From: Markus Schwarz Date: Mon, 8 Aug 2022 22:31:06 +0200 Subject: [PATCH 2/2] move test to detekt-test-utils --- .../rules/KotlinCoreEnvironmentTestSpec.kt | 86 +++++++++++++++++ detekt-test/build.gradle.kts | 2 - .../config/KotlinCoreEnvironmentTestSpec.kt | 94 ------------------- 3 files changed, 86 insertions(+), 96 deletions(-) create mode 100644 detekt-test-utils/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinCoreEnvironmentTestSpec.kt delete mode 100644 detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt diff --git a/detekt-test-utils/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinCoreEnvironmentTestSpec.kt b/detekt-test-utils/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinCoreEnvironmentTestSpec.kt new file mode 100644 index 00000000000..1518c3ebac9 --- /dev/null +++ b/detekt-test-utils/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/KotlinCoreEnvironmentTestSpec.kt @@ -0,0 +1,86 @@ +package io.gitlab.arturbosch.detekt.rules + +import io.github.detekt.test.utils.compileContentForTest +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace +import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny +import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +internal class KotlinCoreEnvironmentTestSpec { + + private val code = """ + import org.assertj.core.api.AbstractAssert + + class CustomAssert(actual: String) : AbstractAssert(actual, CustomAssert::class.java) + """.trimIndent() + + @Nested + @KotlinCoreEnvironmentTest + inner class `Without additional types`(private val env: KotlinCoreEnvironment) { + @Test + fun `no assertj api are available on classpath`() { + val actual = code.compileAndGetSuperTypes(env) + + assertThat(actual).isEmpty() + } + } + + @Nested + inner class `With additional types` { + val expected = listOf("org.assertj.core.api.AbstractAssert") + + @Nested + @KotlinCoreEnvironmentTest(additionalTypes = [AbstractAssert::class]) + inner class `Only addiontial types`(private val env: KotlinCoreEnvironment) { + @Test + fun `types from detekt api are available on classpath`() { + val actual = code.compileAndGetSuperTypes(env) + + assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @KotlinCoreEnvironmentTest(additionalTypes = [AbstractAssert::class, CharRange::class]) + inner class `Also types that are already available`(private val env: KotlinCoreEnvironment) { + @Test + fun `no conflict if types are added multiple times`() { + val actual = code.compileAndGetSuperTypes(env) + + assertThat(actual).isEqualTo(expected) + } + } + } + + private fun String.compileAndGetSuperTypes(env: KotlinCoreEnvironment): List { + val ktFile = compileContentForTest(this) + val ktClass = ktFile.findChildByClass(KtClass::class.java)!! + val bindingContext = env.getContextForPaths(listOf(ktFile)) + + return bindingContext[BindingContext.CLASS, ktClass] + ?.getAllSuperclassesWithoutAny() + ?.map { checkNotNull(it.fqNameOrNull()).toString() } + .orEmpty() + } +} + +// copied from KotlinCoreEnvironmentExtensions.kt +// this is not ideal +private fun KotlinCoreEnvironment.getContextForPaths(paths: List): BindingContext = + TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + this.project, + paths, + NoScopeRecordCliBindingTrace(), + this.configuration, + this::createPackagePartProvider, + ::FileBasedDeclarationProviderFactory + ).bindingContext diff --git a/detekt-test/build.gradle.kts b/detekt-test/build.gradle.kts index c4454e0ed62..1740fae4ac0 100644 --- a/detekt-test/build.gradle.kts +++ b/detekt-test/build.gradle.kts @@ -9,8 +9,6 @@ dependencies { implementation(projects.detektUtils) compileOnly(libs.assertj) implementation(projects.detektCore) - - testImplementation(libs.assertj) } tasks.apiDump { diff --git a/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt b/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt deleted file mode 100644 index 9e82bf1421f..00000000000 --- a/detekt-test/src/test/kotlin/io/gitlab/arturbosch/detekt/core/config/KotlinCoreEnvironmentTestSpec.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.gitlab.arturbosch.detekt.core.config - -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution -import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest -import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext -import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -internal class KotlinCoreEnvironmentTestSpec { - - private val code = """ - import io.gitlab.arturbosch.detekt.api.Config - import io.gitlab.arturbosch.detekt.api.Rule - - class B(config: Config) : Rule(config) { - override val issue = error("I don't care") - } - """.trimIndent() - - private val rule = VerifyTypeHierarchieAvailable() - - @Nested - @KotlinCoreEnvironmentTest - inner class `Without additional types`(private val env: KotlinCoreEnvironment) { - @Test - fun `no types from detekt api are available on classpath`() { - val actual = rule.compileAndLintWithContext(env, code).single() - - assertThat(actual.message).isEqualTo("[]") - } - } - - @Nested - inner class `With additional types` { - val expected = "[" + - "io.gitlab.arturbosch.detekt.api.Rule, " + - "io.gitlab.arturbosch.detekt.api.BaseRule, " + - "io.gitlab.arturbosch.detekt.api.DetektVisitor, " + - "org.jetbrains.kotlin.psi.KtTreeVisitorVoid" + - "]" - - @Nested - @KotlinCoreEnvironmentTest(additionalTypes = [Rule::class]) - inner class `Only addiontial types`(private val env: KotlinCoreEnvironment) { - @Test - fun `types from detekt api are available on classpath`() { - val actual = rule.compileAndLintWithContext(env, code).single() - - assertThat(actual.message).isEqualTo(expected) - } - } - - @Nested - @KotlinCoreEnvironmentTest(additionalTypes = [Rule::class, CharRange::class]) - inner class `Also types that are already available`(private val env: KotlinCoreEnvironment) { - @Test - fun `no conflict if types are added multiple times`() { - val actual = rule.compileAndLintWithContext(env, code).single() - - assertThat(actual.message).isEqualTo(expected) - } - } - } - - @RequiresTypeResolution - private class VerifyTypeHierarchieAvailable : Rule() { - override val issue: Issue = Issue("a", Severity.Minor, "", Debt.FIVE_MINS) - - override fun visitClass(klass: KtClass) { - super.visitClass(klass) - - val superTypes = bindingContext[BindingContext.CLASS, klass] - ?.getAllSuperclassesWithoutAny() - ?.map { checkNotNull(it.fqNameOrNull()).toString() } - .orEmpty() - - report( - CodeSmell(issue, Entity.atName(klass), superTypes.toString()) - ) - } - } -}