diff --git a/demo-project-kotlin/app/src/androidUnitTest/kotlin/org/test/kotlin/app/Rethrow.kt b/demo-project-kotlin/app/src/androidUnitTest/kotlin/org/test/kotlin/app/Rethrow.kt index 3969565..9127f3f 100644 --- a/demo-project-kotlin/app/src/androidUnitTest/kotlin/org/test/kotlin/app/Rethrow.kt +++ b/demo-project-kotlin/app/src/androidUnitTest/kotlin/org/test/kotlin/app/Rethrow.kt @@ -2,5 +2,5 @@ package org.test.kotlin.app actual const val isJVM = true -actual fun rethrow(throwable: () -> Throwable) = +actual fun rethrow(throwable: Throwable) = org.test.kotlin.utils.rethrow(throwable) diff --git a/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/AppOwnersTest.kt b/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/AppOwnersTest.kt index ccc1424..8375914 100644 --- a/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/AppOwnersTest.kt +++ b/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/AppOwnersTest.kt @@ -3,7 +3,6 @@ package org.test.kotlin.app import io.github.gmazzo.codeowners.codeOwners import io.github.gmazzo.codeowners.codeOwnersOf import org.test.kotlin.utils.AppUtils -import kotlin.jvm.JvmSerializableLambda import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -27,7 +26,7 @@ class AppOwnersTest { @Test fun ownerFromExceptionStacktrace() { - val exception = assertFailsWith { rethrow @JvmSerializableLambda { AppException("myException") } } + val exception = assertFailsWith { rethrow(AppException()) } val expectedOwners = setOf(if (isJVM) "utils-devs" else "app-devs") // on JVM we have stackstraces diff --git a/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/Rethrow.kt b/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/Rethrow.kt index 0aa3c4d..abfb935 100644 --- a/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/Rethrow.kt +++ b/demo-project-kotlin/app/src/commonTest/kotlin/org/test/kotlin/app/Rethrow.kt @@ -3,8 +3,8 @@ package org.test.kotlin.app import kotlin.jvm.JvmName -class AppException(message: String) : RuntimeException(message) +class AppException : RuntimeException("anException") expect val isJVM: Boolean -expect fun rethrow(throwable: () -> Throwable) +expect fun rethrow(throwable: Throwable) diff --git a/demo-project-kotlin/app/src/jsTest/kotlin/org/test/kotlin/app/Rethrow.kt b/demo-project-kotlin/app/src/jsTest/kotlin/org/test/kotlin/app/Rethrow.kt index 8a37592..4a6b76e 100644 --- a/demo-project-kotlin/app/src/jsTest/kotlin/org/test/kotlin/app/Rethrow.kt +++ b/demo-project-kotlin/app/src/jsTest/kotlin/org/test/kotlin/app/Rethrow.kt @@ -2,6 +2,6 @@ package org.test.kotlin.app actual const val isJVM = false -actual fun rethrow(throwable: () -> Throwable) { - throw throwable() +actual fun rethrow(throwable: Throwable) { + throw throwable } diff --git a/demo-project-kotlin/app/src/jvmTest/kotlin/org/test/kotlin/app/Rethrow.kt b/demo-project-kotlin/app/src/jvmTest/kotlin/org/test/kotlin/app/Rethrow.kt index 3969565..9127f3f 100644 --- a/demo-project-kotlin/app/src/jvmTest/kotlin/org/test/kotlin/app/Rethrow.kt +++ b/demo-project-kotlin/app/src/jvmTest/kotlin/org/test/kotlin/app/Rethrow.kt @@ -2,5 +2,5 @@ package org.test.kotlin.app actual const val isJVM = true -actual fun rethrow(throwable: () -> Throwable) = +actual fun rethrow(throwable: Throwable) = org.test.kotlin.utils.rethrow(throwable) diff --git a/demo-project-kotlin/app/src/nativeTest/kotlin/org/test/kotlin/app/Rethrow.kt b/demo-project-kotlin/app/src/nativeTest/kotlin/org/test/kotlin/app/Rethrow.kt index 8a37592..4a6b76e 100644 --- a/demo-project-kotlin/app/src/nativeTest/kotlin/org/test/kotlin/app/Rethrow.kt +++ b/demo-project-kotlin/app/src/nativeTest/kotlin/org/test/kotlin/app/Rethrow.kt @@ -2,6 +2,6 @@ package org.test.kotlin.app actual const val isJVM = false -actual fun rethrow(throwable: () -> Throwable) { - throw throwable() +actual fun rethrow(throwable: Throwable) { + throw throwable } diff --git a/demo-project-kotlin/lib1/src/test/kotlin/org/test/kotlin/lib/LibOwnersTest.kt b/demo-project-kotlin/lib1/src/test/kotlin/org/test/kotlin/lib/LibOwnersTest.kt index 2ff10d7..a19357c 100644 --- a/demo-project-kotlin/lib1/src/test/kotlin/org/test/kotlin/lib/LibOwnersTest.kt +++ b/demo-project-kotlin/lib1/src/test/kotlin/org/test/kotlin/lib/LibOwnersTest.kt @@ -21,7 +21,7 @@ class LibOwnersTest { @Test fun ownerOfMoreUtils() { assertEquals(setOf("utils-devs"), MoreUtils::class.java.codeOwners) - assertEquals(setOf("utils-more"), MoreUtils.Companion::class.java.codeOwners) + assertEquals(setOf("utils-devs"), MoreUtils.Companion::class.java.codeOwners) } } diff --git a/demo-project-kotlin/lib2/src/main/kotlin/org/test/kotlin/utils/AndroidLibUtils.kt b/demo-project-kotlin/lib2/src/main/kotlin/org/test/kotlin/utils/AndroidLibUtils.kt index 275f250..1f821b8 100644 --- a/demo-project-kotlin/lib2/src/main/kotlin/org/test/kotlin/utils/AndroidLibUtils.kt +++ b/demo-project-kotlin/lib2/src/main/kotlin/org/test/kotlin/utils/AndroidLibUtils.kt @@ -4,8 +4,6 @@ import io.github.gmazzo.codeowners.CodeOwners sealed class AndroidLibUtils { - // manual override owners - @CodeOwners("libs-impl") data object Impl : AndroidLibUtils() } diff --git a/demo-project-kotlin/lib2/src/test/kotlin/org/test/kotlin/lib/AndroidLibOwnersTest.kt b/demo-project-kotlin/lib2/src/test/kotlin/org/test/kotlin/lib/AndroidLibOwnersTest.kt index dd58a3e..ecc9cda 100644 --- a/demo-project-kotlin/lib2/src/test/kotlin/org/test/kotlin/lib/AndroidLibOwnersTest.kt +++ b/demo-project-kotlin/lib2/src/test/kotlin/org/test/kotlin/lib/AndroidLibOwnersTest.kt @@ -10,7 +10,7 @@ class AndroidLibOwnersTest { @Test fun ownerOfAndroidLib() { assertEquals(setOf("libs-devs"), codeOwnersOf()) - assertEquals(setOf("libs-impl"), codeOwnersOf()) + assertEquals(setOf("libs-devs"), codeOwnersOf()) } } diff --git a/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/Rethrow.kt b/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/Rethrow.kt index 2b1e92b..feef934 100644 --- a/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/Rethrow.kt +++ b/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/Rethrow.kt @@ -1,5 +1,5 @@ package org.test.kotlin.utils -fun rethrow(throwable: () -> Throwable) { - throw throwable() +fun rethrow(throwable: Throwable) { + throw throwable.javaClass.newInstance() } diff --git a/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/more/MoreUtils.kt b/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/more/MoreUtils.kt index 08fe412..1e6ec49 100644 --- a/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/more/MoreUtils.kt +++ b/demo-project-kotlin/utils/src/main/kotlin/org/test/kotlin/utils/more/MoreUtils.kt @@ -1,11 +1,7 @@ package org.test.kotlin.utils.more -import io.github.gmazzo.codeowners.CodeOwners - class MoreUtils { - // manual override owners - @CodeOwners("utils-more") companion object } diff --git a/demo-project-kotlin/utils/src/test/java/org/test/kotlin/utils/UtilsOwnersTest.java b/demo-project-kotlin/utils/src/test/java/org/test/kotlin/utils/UtilsOwnersTest.java index 9a5b13b..c9312f9 100644 --- a/demo-project-kotlin/utils/src/test/java/org/test/kotlin/utils/UtilsOwnersTest.java +++ b/demo-project-kotlin/utils/src/test/java/org/test/kotlin/utils/UtilsOwnersTest.java @@ -17,7 +17,7 @@ public void ownerOfUtils() { @Test public void ownerOfMoreUtils() { assertEquals(setOf("utils-devs"), getCodeOwners(MoreUtils.class)); - assertEquals(setOf("utils-more"), getCodeOwners(MoreUtils.Companion.class)); + assertEquals(setOf("utils-devs"), getCodeOwners(MoreUtils.Companion.class)); } } diff --git a/demo-project-tests/build.gradle.kts b/demo-project-tests/build.gradle.kts index d5b6916..b98cea3 100644 --- a/demo-project-tests/build.gradle.kts +++ b/demo-project-tests/build.gradle.kts @@ -17,21 +17,23 @@ testing.suites.withType().configureEach { useJUnitJupiter() } -val collectTaskOutputs = copySpec() val collectTask = tasks.register("collectTaskOutputs") { - with(collectTaskOutputs) into(temporaryDir) } rootProject.allprojects project@{ tasks.withType().all task@{ - collectTaskOutputs.from(files(simplifiedMappedCodeOwnersFile, rawMappedCodeOwnersFile)) { - into("actualMappings/${project.path}") + collectTask.configure { + from(listOf(simplifiedMappedCodeOwnersFile, rawMappedCodeOwnersFile)) { + into("actualMappings/${this@task.project.path}") + } } } tasks.withType().all task@{ - collectTaskOutputs.from(codeOwnersReportFile) { - into("actualReports/${project.path}") + collectTask.configure { + from(codeOwnersReportFile) { + into("actualReports/${this@task.project.path}") + } } } } diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidDebugUnitTest.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidDebugUnitTest.codeowners index 230bf91..226a856 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidDebugUnitTest.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidDebugUnitTest.codeowners @@ -1,6 +1,6 @@ # CodeOwners of module ':demo-project-kotlin:app' (source set 'androidDebugUnitTest') -org/test/kotlin/app/AppException app-devs -org/test/kotlin/app/AppOwnersTest app-devs -org/test/kotlin/app/AppOwnersTest$ownerFromExceptionStacktrace$exception$1$1 app-devs -org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/AppException app-devs +org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidReleaseUnitTest.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidReleaseUnitTest.codeowners index feeb27b..c4fb4fe 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidReleaseUnitTest.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-androidReleaseUnitTest.codeowners @@ -1,6 +1,6 @@ # CodeOwners of module ':demo-project-kotlin:app' (source set 'androidReleaseUnitTest') -org/test/kotlin/app/AppException app-devs -org/test/kotlin/app/AppOwnersTest app-devs -org/test/kotlin/app/AppOwnersTest$ownerFromExceptionStacktrace$exception$1$1 app-devs -org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/AppException app-devs +org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosArm64Test.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosArm64Test.codeowners index dfa8d89..50342db 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosArm64Test.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosArm64Test.codeowners @@ -2,3 +2,5 @@ org/test/kotlin/app/AppException app-devs org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosSimulatorArm64Test.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosSimulatorArm64Test.codeowners index 6b4532a..11092e8 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosSimulatorArm64Test.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-iosSimulatorArm64Test.codeowners @@ -2,3 +2,5 @@ org/test/kotlin/app/AppException app-devs org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jsTest.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jsTest.codeowners index 33db08d..f2d490f 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jsTest.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jsTest.codeowners @@ -2,3 +2,5 @@ org/test/kotlin/app/AppException app-devs org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jvmTest.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jvmTest.codeowners index 37c7205..5dbefdf 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jvmTest.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app-jvmTest.codeowners @@ -1,8 +1,8 @@ # CodeOwners of module ':demo-project-kotlin:app' (source set 'jvmTest') -org/test/kotlin/app/AppException app-devs -org/test/kotlin/app/AppOwnersJVMTest app-devs -org/test/kotlin/app/AppOwnersJVMTest$ownerOfUtilFunctions$1 app-devs -org/test/kotlin/app/AppOwnersTest app-devs -org/test/kotlin/app/AppOwnersTest$ownerFromExceptionStacktrace$exception$1$1 app-devs -org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/AppException app-devs +org/test/kotlin/app/AppOwnersJVMTest app-devs +org/test/kotlin/app/AppOwnersJVMTest$ownerOfUtilFunctions$1 app-devs +org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs diff --git a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app.codeowners b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app.codeowners index c342450..d38bd9c 100644 --- a/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app.codeowners +++ b/demo-project-tests/src/test/resources/expectedReports/:demo-project-kotlin:app/app.codeowners @@ -1,12 +1,12 @@ # CodeOwners of module ':demo-project-kotlin:app' -org/test/kotlin/app/AppClass app-devs -org/test/kotlin/app/AppException app-devs -org/test/kotlin/app/AppOwnersJVMTest app-devs -org/test/kotlin/app/AppOwnersJVMTest$ownerOfUtilFunctions$1 app-devs -org/test/kotlin/app/AppOwnersTest app-devs -org/test/kotlin/app/AppOwnersTest$ownerFromExceptionStacktrace$exception$1$1 app-devs -org/test/kotlin/app/BuildConfig app-devs -org/test/kotlin/app/RethrowKt app-devs -org/test/kotlin/app/test/BuildConfig app-devs -org/test/kotlin/utils/AppUtils app-devs +org/test/kotlin/app/AppClass app-devs +org/test/kotlin/app/AppException app-devs +org/test/kotlin/app/AppOwnersJVMTest app-devs +org/test/kotlin/app/AppOwnersJVMTest$ownerOfUtilFunctions$1 app-devs +org/test/kotlin/app/AppOwnersTest app-devs +org/test/kotlin/app/BuildConfig app-devs +org/test/kotlin/app/RethrowKt app-devs +org/test/kotlin/app/RethrowUtils app-devs +org/test/kotlin/app/test/BuildConfig app-devs +org/test/kotlin/utils/AppUtils app-devs diff --git a/gradle.properties b/gradle.properties index 29ef11a..15cd1cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,6 @@ org.gradle.jvmargs=-Xmx2g org.gradle.caching=true org.gradle.configuration-cache=true org.gradle.parallel=true + +# facilitates debugging +# kotlin.compiler.execution.strategy=in-process diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersComponentRegistrar.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersComponentRegistrar.kt index dae7ba9..507e107 100644 --- a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersComponentRegistrar.kt +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersComponentRegistrar.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlin.ir.util.IrMessageLogger import org.jetbrains.kotlin.ir.util.irMessageLogger @@ -31,11 +32,12 @@ internal class CodeOwnersComponentRegistrar : CompilerPluginRegistrar() { val codeOwnersRoot = configuration.get(CODEOWNERS_ROOT)!! val codeOwnersFile = configuration.get(CODEOWNERS_FILE)!!.useLines { CodeOwnersFile(it) } val mappingFile = configuration.get(MAPPINGS_OUTPUT) + val matcher = CodeOwnersMatcher(codeOwnersRoot, codeOwnersFile) + val mappings = CodeOwnersMappings(matcher, mappingFile) - mappingFile?.delete() - mappingFile?.parentFile?.mkdirs() - IrGenerationExtension.registerExtension(CodeOwnersIrGenerationExtension(matcher, mappingFile)) + FirExtensionRegistrarAdapter.registerExtension(CodeOwnersFirExtensionRegistrar(mappings)) + IrGenerationExtension.registerExtension(CodeOwnersIrGenerationExtension(mappings)) } } diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirExtensionRegistrar.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirExtensionRegistrar.kt new file mode 100644 index 0000000..a5ccdab --- /dev/null +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirExtensionRegistrar.kt @@ -0,0 +1,14 @@ +package io.github.gmazzo.codeowners.compiler + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar + +class CodeOwnersFirExtensionRegistrar( + private val mappings: CodeOwnersMappings, +) : FirExtensionRegistrar() { + + override fun ExtensionRegistrarContext.configurePlugin() { + +{ session: FirSession -> CodeOwnersFirProcessor(session, mappings) } + } + +} diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirProcessor.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirProcessor.kt new file mode 100644 index 0000000..d47e3e4 --- /dev/null +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersFirProcessor.kt @@ -0,0 +1,63 @@ +package io.github.gmazzo.codeowners.compiler + +import org.jetbrains.kotlin.backend.common.serialization.toIoFileOrNull +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFileChecker +import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.utils.classId +import org.jetbrains.kotlin.fir.java.findJvmNameValue +import org.jetbrains.kotlin.fir.packageFqName +import org.jetbrains.kotlin.fir.visitors.FirVisitor +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.jvm.JvmClassName + +class CodeOwnersFirProcessor( + session: FirSession, + private val mappings: CodeOwnersMappings, +) : FirAdditionalCheckersExtension(session) { + + override val declarationCheckers = object : DeclarationCheckers() { + override val fileCheckers = setOf(CodeOwnersMapper()) + } + + inner class CodeOwnersMapper : FirFileChecker(MppCheckerKind.Platform) { + override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) { + val mappings = declaration.sourceFile?.toIoFileOrNull()?.let(mappings::resolve) ?: return + + declaration.accept(fileVisitor, mappings) + } + } + + private val fileVisitor = object : FirVisitor() { + + override fun visitFile(file: FirFile, data: CodeOwnersMappings.Mapping) { + file.acceptChildren(this, data) + + if (file.declarations.any { it is FirSimpleFunction }) { + val fileJvmName = file.findJvmNameValue() ?: file.name.replace("\\.kt".toRegex(), "Kt") + val fileClass = JvmClassName.byFqNameWithoutInnerClasses(file.packageFqName.child(Name.identifier(fileJvmName))).internalName + + data.classes += fileClass + } + } + + override fun visitRegularClass(regularClass: FirRegularClass, data: CodeOwnersMappings.Mapping) { + data.classes += JvmClassName.byClassId(regularClass.classId).internalName + + regularClass.acceptChildren(this, data) + } + + override fun visitElement(element: FirElement, data: CodeOwnersMappings.Mapping) { + } + + } + +} diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrGenerationExtension.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrGenerationExtension.kt index cb7f2e5..e32d326 100644 --- a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrGenerationExtension.kt +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrGenerationExtension.kt @@ -1,34 +1,23 @@ package io.github.gmazzo.codeowners.compiler -import io.github.gmazzo.codeowners.matcher.CodeOwnersFile -import io.github.gmazzo.codeowners.matcher.CodeOwnersMatcher import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import java.io.File internal class CodeOwnersIrGenerationExtension( - private val matcher: CodeOwnersMatcher, - private val mappingsFile: File?, + private val mappings: CodeOwnersMappings, ) : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - val mappings = mappingsFile?.let { mutableMapOf>() } - val transformer = CodeOwnersIrTransformer(pluginContext, mappings) - val owners = moduleFragment.files.asSequence() - .flatMap { matcher.ownerOf(File(it.fileEntry.name)).orEmpty() } - .toSet() + mappings.noteFrontedFinished() - if (owners.isNotEmpty()) { - moduleFragment.accept(transformer, owners) + val transformer = CodeOwnersIrTransformer(pluginContext, mappings) - if (mappings != null) { - val entries = mappings.map { (file, owners) -> CodeOwnersFile.Entry(file, owners.toList()) } - val codeOwners = CodeOwnersFile(entries) + moduleFragment.accept(transformer, InvalidOwners) + } - mappingsFile!!.appendText(codeOwners.content) - } - } + private data object InvalidOwners : Set by emptySet() { + override val size: Int get() = error("Invalid owners") } } diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrTransformer.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrTransformer.kt index d69fb3f..7495513 100644 --- a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrTransformer.kt +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersIrTransformer.kt @@ -1,24 +1,19 @@ +@file:OptIn(UnsafeDuringIrConstructionAPI::class) + package io.github.gmazzo.codeowners.compiler import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext -import org.jetbrains.kotlin.backend.jvm.ir.getKtFile +import org.jetbrains.kotlin.backend.jvm.ir.getIoFile import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DescriptorVisibilities -import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil -import org.jetbrains.kotlin.ir.IrElementBase -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.utils.nameWithoutExtension -import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments import org.jetbrains.kotlin.ir.builders.declarations.addConstructor import org.jetbrains.kotlin.ir.builders.declarations.buildClass -import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrMutableAnnotationContainer -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.createBlockBody -import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrVararg import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl @@ -27,33 +22,28 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.types.classFqName +import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI import org.jetbrains.kotlin.ir.types.classifierOrFail import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.types.starProjectedType import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET import org.jetbrains.kotlin.ir.util.addChild -import org.jetbrains.kotlin.ir.util.classId import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.ir.util.fileOrNull -import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable import org.jetbrains.kotlin.ir.util.primaryConstructor import org.jetbrains.kotlin.ir.visitors.IrElementTransformer import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.isJs import org.jetbrains.kotlin.platform.konan.isNative -import org.jetbrains.kotlin.resolve.jvm.JvmClassName.byClassId internal class CodeOwnersIrTransformer( private val context: IrPluginContext, - private val mappings: MutableMap>?, + private val mappings: CodeOwnersMappings, ) : IrElementTransformer> { - private val isJS = context.platform?.isJs() == true - - private val isNative = context.platform?.isNative() == true + private val requiresProvider = context.platform?.let { it.isJs() || it.isNative() } == true private val stringArray = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType) @@ -69,44 +59,37 @@ internal class CodeOwnersIrTransformer( context.referenceClass(ClassId.fromString("io/github/gmazzo/codeowners/CodeOwnersProvider"))!! } - private val fileCodeOwnersProviders = mutableMapOf, IrClassSymbol>() + private var fileCodeOwnersProvider: IrClassSymbol? = null override fun visitFile(declaration: IrFile, data: Set) = declaration.apply { - addAnnotation(data) - - super.visitFile(declaration, data) - fileCodeOwnersProviders.clear() - } + val owners = declaration.getIoFile()?.let(mappings::resolve)?.owners ?: return@apply - override fun visitSimpleFunction(declaration: IrSimpleFunction, data: Set): IrStatement { - (declaration.parent as? IrFile)?.exportMapping(data) + addAnnotation(owners) + super.visitFile(declaration, owners) - return super.visitFunction(declaration, data) + fileCodeOwnersProvider = null } override fun visitClass(declaration: IrClass, data: Set) = declaration.apply { - val ownersValue = addAnnotation(data) + // we only decorate top level classes + if (parent !is IrFile) return@apply - if (isJS || isNative) { - fileOrNull?.let { file -> - val provider = file.getOrCreateCodeOwnersProvider(data, ownersValue) + val ownersValue = addAnnotation(data) - addCodeOwnersProviderAnnotation(provider) - } + if (requiresProvider) { + addCodeOwnersProviderAnnotation(ownersValue) } - exportMapping(data) super.visitClass(declaration, data) } private fun IrMutableAnnotationContainer.addAnnotation(owners: Set): IrVararg { - val exisingOwners = findOwnersFromExistingAnnotation() val ownersValue = IrVarargImpl( UNDEFINED_OFFSET, UNDEFINED_OFFSET, stringArray, context.irBuiltIns.stringType, - (exisingOwners ?: owners).map { value -> + owners.map { value -> IrConstImpl.string( UNDEFINED_OFFSET, UNDEFINED_OFFSET, @@ -116,36 +99,17 @@ internal class CodeOwnersIrTransformer( }, ) - if (exisingOwners == null) { - annotations += IrConstructorCallImpl.fromSymbolOwner( - SYNTHETIC_OFFSET, - SYNTHETIC_OFFSET, - annotationCodeOwners.defaultType, - annotationCodeOwners.owner.primaryConstructor!!.symbol, - ).apply { - putValueArgument(0, ownersValue) - } + annotations += IrConstructorCallImpl.fromSymbolOwner( + SYNTHETIC_OFFSET, + SYNTHETIC_OFFSET, + annotationCodeOwners.defaultType, + annotationCodeOwners.owner.primaryConstructor!!.symbol, + ).apply { + putValueArgument(0, ownersValue) } return ownersValue } - private fun IrAnnotationContainer.findOwnersFromExistingAnnotation(): Set? { - val annotation = annotations - .find { it.type.classFqName == annotationCodeOwners.owner.fqNameWhenAvailable } ?: return null - - return (annotation.valueArguments[0] as IrVararg).elements.mapTo(linkedSetOf()) { - @Suppress("UNCHECKED_CAST") - (it as IrConst).value - } - } - - private fun IrFile.getOrCreateCodeOwnersProvider( - owners: Set, - ownersValue: IrVararg, - ) = fileCodeOwnersProviders.getOrPut(owners) { - addCodeOwnersProvider("${nameWithoutExtension}\$CODEOWNERS_${fileCodeOwnersProviders.size}", ownersValue) - } - private fun IrFile.addCodeOwnersProvider(className: String, owners: IrVararg) = context.irFactory.buildClass { startOffset = SYNTHETIC_OFFSET endOffset = SYNTHETIC_OFFSET @@ -182,37 +146,31 @@ internal class CodeOwnersIrTransformer( ) }.symbol - private fun IrClass.addCodeOwnersProviderAnnotation(provider: IrClassSymbol) { + private fun IrClass.addCodeOwnersProviderAnnotation(ownersValue: IrVararg) { + if (fileCodeOwnersProvider == null) { + val file = fileOrNull ?: return + + fileCodeOwnersProvider = file.addCodeOwnersProvider("${file.nameWithoutExtension}\$CODEOWNERS", ownersValue) + } + annotations += IrConstructorCallImpl.fromSymbolOwner( UNDEFINED_OFFSET, UNDEFINED_OFFSET, annotationCodeOwnersProvider.defaultType, annotationCodeOwnersProvider.owner.primaryConstructor!!.symbol, ).apply { + val starType = fileCodeOwnersProvider!!.starProjectedType + putValueArgument( 0, IrClassReferenceImpl( UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.kClassClass.starProjectedType, - provider.starProjectedType.classifierOrFail, - provider.starProjectedType, + starType.classifierOrFail, + starType, ) ) } } - private fun IrElementBase.exportMapping(owners: Set) { - if (mappings != null) { - val name = jvmName() ?: return - - mappings.computeIfAbsent(name) { mutableSetOf() }.addAll(owners) - } - } - - private fun IrElementBase.jvmName(): String? = when (this) { - is IrFile -> getKtFile()?.let(JvmFileClassUtil::getFileClassInternalName) - is IrClass -> classId?.let(::byClassId)?.internalName - else -> null - } - } diff --git a/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersMappings.kt b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersMappings.kt new file mode 100644 index 0000000..0b0dc72 --- /dev/null +++ b/plugins/kotlin-compiler/src/main/kotlin/io/github/gmazzo/codeowners/compiler/CodeOwnersMappings.kt @@ -0,0 +1,40 @@ +package io.github.gmazzo.codeowners.compiler + +import io.github.gmazzo.codeowners.matcher.CodeOwnersFile +import io.github.gmazzo.codeowners.matcher.CodeOwnersMatcher +import java.io.File + +class CodeOwnersMappings( + private val matcher: CodeOwnersMatcher, + private var mappingFile: File?, +) { + + private val mappings = mutableMapOf() + + fun resolve(file: File) = mappings.computeIfAbsent(file) { + matcher.ownerOf(file)?.let { Mapping(owners = it) } + } + + fun noteFrontedFinished() = mappingFile?.let { file -> + mappingFile = null + + val entries = mappings.entries.asSequence() + .onEach { (file) -> check(file.isFile) { "$file does not exists!" } } + .filter { it.value != null } + .flatMap { (_, mapping) -> + mapping!!.classes.asSequence().map { + CodeOwnersFile.Entry(pattern = it, mapping.owners.toList()) + } + } + .toList() + + file.parentFile?.mkdirs() + file.writeText(CodeOwnersFile(entries).content) + } + + data class Mapping( + val classes: MutableSet = mutableSetOf(), + val owners: Set, + ) + +} diff --git a/plugins/kotlin-core/build.gradle.kts b/plugins/kotlin-core/build.gradle.kts index e8c0560..babae36 100644 --- a/plugins/kotlin-core/build.gradle.kts +++ b/plugins/kotlin-core/build.gradle.kts @@ -1,6 +1,4 @@ import org.gradle.configurationcache.extensions.capitalized -import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink -import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink plugins { alias(libs.plugins.kotlin.multiplatform) diff --git a/plugins/kotlin-core/src/jvmMain/kotlin/io/github/gmazzo/codeowners/CodeOwners.kt b/plugins/kotlin-core/src/jvmMain/kotlin/io/github/gmazzo/codeowners/CodeOwners.kt index d1d7994..b86b519 100644 --- a/plugins/kotlin-core/src/jvmMain/kotlin/io/github/gmazzo/codeowners/CodeOwners.kt +++ b/plugins/kotlin-core/src/jvmMain/kotlin/io/github/gmazzo/codeowners/CodeOwners.kt @@ -13,7 +13,7 @@ actual val KClass<*>.codeOwners: Set? val Class<*>.codeOwners: Set? get() = when { Proxy.isProxyClass(this) -> resolveInterfacesIfProxy().mapNotNull { it.codeOwners }.flatten().toSet() - else -> getAnnotation(CodeOwners::class.java)?.owners?.toSet() + else -> getAnnotation(CodeOwners::class.java)?.owners?.toSet() ?: enclosingClass?.codeOwners } val KFunction<*>.codeOwners: Set? diff --git a/plugins/kotlin-plugin/src/main/kotlin/io/github/gmazzo/codeowners/CodeOwnersKotlinPlugin.kt b/plugins/kotlin-plugin/src/main/kotlin/io/github/gmazzo/codeowners/CodeOwnersKotlinPlugin.kt index 9539cb1..a748931 100644 --- a/plugins/kotlin-plugin/src/main/kotlin/io/github/gmazzo/codeowners/CodeOwnersKotlinPlugin.kt +++ b/plugins/kotlin-plugin/src/main/kotlin/io/github/gmazzo/codeowners/CodeOwnersKotlinPlugin.kt @@ -10,6 +10,7 @@ import io.github.gmazzo.codeowners.BuildConfig.COMPILER_PLUGIN_ID import io.github.gmazzo.codeowners.BuildConfig.CORE_DEPENDENCY import io.github.gmazzo.codeowners.KotlinSupport.Companion.codeOwnersSourceSetName import org.gradle.api.Project +import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.codeOwners import org.gradle.kotlin.dsl.newInstance @@ -38,6 +39,10 @@ class CodeOwnersKotlinPlugin : override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> = with(kotlinCompilation.project.the()) { + kotlinCompilation.compileTaskProvider.configure { + outputs.dir(kotlinCompilation.outputMappingsFile.map { it.asFile.parentFile }).optional() + } + rootDirectory .zip(codeOwnersFile.zip(kotlinCompilation.outputMappingsFile, ::Pair), ::Pair).map { (root, it) -> val (file, mappings) = it @@ -50,8 +55,11 @@ class CodeOwnersKotlinPlugin : } } - private val KotlinCompilation<*>.outputMappingsFile - get() = project.layout.buildDirectory.file("codeowners/mappings/${project.name}-$codeOwnersSourceSetName.codeowners") + private val KotlinCompilation<*>.outputMappingsFile: Provider + get() { + val name = "${project.name}-$codeOwnersSourceSetName" + return project.layout.buildDirectory.file("codeowners/mappings/$name/$name.codeowners") + } override fun Project.configureExtension(extension: CodeOwnersKotlinExtension) = KotlinSupport(this).configureTargets target@{