diff --git a/CHANGELOG.md b/CHANGELOG.md index a171e98..cbabdae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,6 @@ Change Log ========== ## Version 0.1.0 -_2024-07-15_ +_2024-10-13_ * Initial release diff --git a/README.md b/README.md index f26f305..4820d38 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ configure { Annotate( annotationsToAdd = listOf(Annotation(fqName = "kotlin.js.JsExport")), packageTarget = listOf(PackageTarget(pattern = "com.project.common.dto")), - annotationsTarget = listOf(AnnotationTarget.CLASS), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), sourceSets = listOf("commonMain", "jsMain"), ), ) @@ -76,14 +76,14 @@ Configure the plugin in your `build.gradle.kts` file: ```kotlin import dev.shalaga44.mat.* -configure { +configure { annotations = listOf( Annotate( - annotationsToAdd = listOf(Annotation("kotlin.js.JsExport")), - annotationsTarget = listOf(AnnotationTarget.CLASS), - packageTarget = listOf(PackageTarget("com.project.common.dto")), + annotationsToAdd = listOf(Annotation(fqName = "kotlin.js.JsExport")), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), + packageTarget = listOf(PackageTarget(pattern = "com.project.common.dto")), sourceSets = listOf("commonMain", "jsMain"), - ), + ) ) } ``` @@ -95,7 +95,7 @@ kotlinMissingAnnotationsTherapist { annotations = [ Annotate( annotationsToAdd = [Annotation("kotlin.js.JsExport")], - annotationsTarget = [AnnotationTarget.CLASS], + classTargets = [ClassTypeTarget.REGULAR_CLASS], packageTarget = [PackageTarget("com.project.common.dto")], sourceSets = ["commonMain", "jsMain"] ) @@ -110,7 +110,7 @@ using the correct version of this plugin for your version of Kotlin. Check the t | Kotlin Version | Plugin Version | |----------------|----------------| -| 2.0.21 | 0.0.1 | +| 2.0.21 | 0.1.0 | | 2.1.0 | soon | ## Kotlin IR @@ -125,7 +125,7 @@ target { } ``` -A working example is will be provided soon +Read the tests for use cases. A working example is [[available]](https://github.com/shalaga44/missing-annotations-therapist/tree/main/sample) in this repository in the sample directory. diff --git a/build.gradle.kts b/build.gradle.kts index a53a509..70e4c85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ plugins { allprojects { group = "io.github.shalaga44" - version = "0.0.2" + version = "0.1.0" repositories { mavenCentral() diff --git a/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradleExtension.kt b/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradleExtension.kt index c443118..46785c3 100644 --- a/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradleExtension.kt +++ b/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradleExtension.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package dev.shalaga44.mat +package dev.shalaga44.mat.utils /** * Represents an annotation to be added. * * @property fqName Fully qualified name of the annotation. - * @property parameters Key-value pairs for annotation parameters. Supports dynamic templates. */ data class Annotation( val fqName: String, - val parameters: Map = emptyMap(), ) { val shortName: String = fqName.substringAfterLast(".") } @@ -40,9 +38,7 @@ data class PackageTarget( val pattern: String, val matchType: MatchType = MatchType.EXACT, val regex: String? = null, -) { - val shortName: String = pattern.substringAfterLast(".") -} +) /** * Specifies conditions under which annotations should be applied. @@ -104,7 +100,7 @@ data class ModuleTarget( enum class MatchType { EXACT, WILDCARD, - REGEX, + REGEX } /** @@ -114,7 +110,7 @@ enum class Visibility { PUBLIC, PRIVATE, PROTECTED, - INTERNAL, + INTERNAL } /** @@ -125,34 +121,91 @@ enum class Modifier { FINAL, ABSTRACT, SUSPEND, + PRIVATE, +} + +/** + * Defines different types of class targets for annotations. + */ +enum class ClassTypeTarget { + REGULAR_CLASS, + ENUM_CLASS, + SEALED_CLASS, + DATA_CLASS, + OBJECT_CLASS, + ANNOTATION_CLASS, +} + +/** + * Defines different function types for annotations. + */ +enum class FunctionTypeTarget { + FUNCTION, + SUSPEND_FUNCTION, + LAMBDA, + CONSTRUCTOR, +} + +/** + * Defines different property targets for annotations. + */ +enum class PropertyTypeTarget { + PROPERTY, + FIELD, + LOCAL_VARIABLE, + VALUE_PARAMETER, + GETTER, + SETTER, +} + +/** + * Defines type alias targets for annotations. + */ +enum class TypeAliasTarget { + TYPE_ALIAS, +} + +/** + * Defines file-level targets for annotations. + */ +enum class FileTarget { + FILE, } /** * Represents an annotation addition rule. * * @property annotationsToAdd List of annotations to add. - * @property annotationsTarget List of Kotlin annotation targets (CLASS, FUNCTION, PROPERTY, etc.). + * @property classTargets List of class types to apply annotations to. + * @property functionTargets List of function types to apply annotations to. + * @property propertyTargets List of property types to apply annotations to. + * @property typeAliasTargets List of type alias targets. + * @property fileTargets List of file targets. * @property packageTarget List of package targets where annotations should be applied. * @property moduleTarget List of module targets to include or exclude. * @property sourceSets List of source sets to apply the annotations to. * @property conditions List of conditions that must be met to apply the annotations. - * @property annotateNestedClasses Should annotate nested classes. - * @property annotateFieldClasses shouldAnnotateFieldClasses. + * @property annotateNestedClasses If true, nested classes will also be annotated. + * @property annotateFieldClasses If true, field-referenced classes will also be annotated. */ data class Annotate( var annotationsToAdd: List, - var annotationsTarget: List = listOf(), + var classTargets: List = listOf(), + var functionTargets: List = listOf(), + var propertyTargets: List = listOf(), + var typeAliasTargets: List = listOf(), + var fileTargets: List = listOf(), var packageTarget: List, var moduleTarget: List = listOf(), var sourceSets: List = listOf(), var conditions: List = listOf(), - var annotateNestedClasses: Boolean = true, - var annotateFieldClasses: Boolean = true, + val annotateNestedClasses: Boolean = false, + val annotateFieldClasses: Boolean = false, ) /** * Gradle extension for configuring the MissingAnnotationsTherapist compiler plugin. */ -open class MissingAnnotationsTherapistGradleExtension { +open class MissingAnnotationsTherapist { var annotations: List = listOf() } diff --git a/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradlePlugin.kt b/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradlePlugin.kt index 96cef36..8ab6daf 100644 --- a/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradlePlugin.kt +++ b/missing-annotations-therapist-gradle/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistGradlePlugin.kt @@ -4,6 +4,7 @@ package dev.shalaga44.mat import com.google.gson.Gson import com.google.gson.GsonBuilder +import dev.shalaga44.mat.utils.MissingAnnotationsTherapist import org.gradle.api.Project import org.gradle.api.provider.Provider import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation @@ -18,16 +19,16 @@ class MissingAnnotationsTherapistGradlePlugin : KotlinCompilerPluginSupportPlugi const val COMPILER_PLUGIN_ID = "io.github.shalaga44.missing-annotations-therapist" const val PLUGIN_GROUP_ID = "io.github.shalaga44" const val PLUGIN_ARTIFACT_ID = "missing-annotations-therapist-plugin" - const val PLUGIN_VERSION = "0.0.2" + const val PLUGIN_VERSION = "0.1.0" } override fun apply(target: Project): Unit = with(target) { - extensions.create("kotlinMissingAnnotationsTherapist", MissingAnnotationsTherapistGradleExtension::class.java) + extensions.create("kotlinMissingAnnotationsTherapist", MissingAnnotationsTherapist::class.java) } override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { val project = kotlinCompilation.target.project - val extension = project.extensions.findByName("kotlinMissingAnnotationsTherapist") as? MissingAnnotationsTherapistGradleExtension ?: return false + val extension = project.extensions.findByName("kotlinMissingAnnotationsTherapist") as? MissingAnnotationsTherapist ?: return false val currentSourceSet = kotlinCompilation.defaultSourceSet.name return extension.annotations.any { it.sourceSets.contains(currentSourceSet) } } @@ -43,7 +44,7 @@ class MissingAnnotationsTherapistGradlePlugin : KotlinCompilerPluginSupportPlugi override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { val project = kotlinCompilation.target.project val extension = - project.extensions.findByName("kotlinMissingAnnotationsTherapist") as? MissingAnnotationsTherapistGradleExtension + project.extensions.findByName("kotlinMissingAnnotationsTherapist") as? MissingAnnotationsTherapist ?: return project.provider { emptyList() } val gson = GsonBuilder() diff --git a/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistFIrCompilerPluginRegistrar.kt b/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistFIrCompilerPluginRegistrar.kt index cb23b32..65898df 100644 --- a/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistFIrCompilerPluginRegistrar.kt +++ b/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/MissingAnnotationsTherapistFIrCompilerPluginRegistrar.kt @@ -1,15 +1,25 @@ +/* + * Copyright (C) 2020 Brian Norman + * + * 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 dev.shalaga44.mat import com.google.auto.service.AutoService import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import dev.shalaga44.mat.utils.Annotate -import dev.shalaga44.mat.utils.Annotation -import dev.shalaga44.mat.utils.Condition -import dev.shalaga44.mat.utils.MatchType -import dev.shalaga44.mat.utils.Modifier -import dev.shalaga44.mat.utils.PackageTarget -import dev.shalaga44.mat.utils.Visibility +import dev.shalaga44.mat.utils.* import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration @@ -18,28 +28,11 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter 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.FirPropertyChecker -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirRegularClassChecker -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirSimpleFunctionChecker +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol import org.jetbrains.kotlin.fir.analysis.checkers.hasModifier import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension -import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer -import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration -import org.jetbrains.kotlin.fir.declarations.FirClass -import org.jetbrains.kotlin.fir.declarations.FirCodeFragment -import org.jetbrains.kotlin.fir.declarations.FirDanglingModifierList -import org.jetbrains.kotlin.fir.declarations.FirDeclaration -import org.jetbrains.kotlin.fir.declarations.FirFile -import org.jetbrains.kotlin.fir.declarations.FirProperty -import org.jetbrains.kotlin.fir.declarations.FirRegularClass -import org.jetbrains.kotlin.fir.declarations.FirScript -import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction -import org.jetbrains.kotlin.fir.declarations.FirTypeAlias -import org.jetbrains.kotlin.fir.declarations.FirTypeParameter -import org.jetbrains.kotlin.fir.declarations.FirVariable -import org.jetbrains.kotlin.fir.declarations.hasAnnotation +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.classId import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes import org.jetbrains.kotlin.fir.expressions.FirAnnotation @@ -66,7 +59,6 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name - @AutoService(CompilerPluginRegistrar::class) class MissingAnnotationsTherapistCompilerPluginRegistrar( private val args: MissingAnnotationsTherapistArgs? = null, @@ -140,7 +132,7 @@ internal class FirMissingAnnotationsTherapistExtensionSessionComponent( val enableLogging: Boolean = args.enableLogging fun shouldAnnotateClass(declaration: FirRegularClass): Boolean { - val should = shouldAnnotate(declaration.classId.packageFqName.asString()) + val should = shouldAnnotate(declaration.classId.packageFqName.asString(), ClassTypeTarget.REGULAR_CLASS) if (enableLogging) { println("Checking if should annotate class ${declaration.name}: $should") } @@ -149,7 +141,8 @@ internal class FirMissingAnnotationsTherapistExtensionSessionComponent( fun shouldAnnotateFunction(declaration: FirSimpleFunction): Boolean { val containingClass = declaration.getContainingClass(session) - val should = shouldAnnotate(containingClass?.classId?.packageFqName?.asString() ?: "") + val packageName = containingClass?.classId?.packageFqName?.asString() ?: "" + val should = shouldAnnotate(packageName, FunctionTypeTarget.FUNCTION) if (enableLogging) { println("Checking if should annotate function ${declaration.name}: $should") } @@ -158,7 +151,8 @@ internal class FirMissingAnnotationsTherapistExtensionSessionComponent( fun shouldAnnotateProperty(declaration: FirProperty): Boolean { val containingClass = declaration.getContainingClass(session) - val should = shouldAnnotate(containingClass?.classId?.packageFqName?.asString() ?: "") + val packageName = containingClass?.classId?.packageFqName?.asString() ?: "" + val should = shouldAnnotate(packageName, PropertyTypeTarget.PROPERTY) if (enableLogging) { println("Checking if should annotate property ${declaration.name}: $should") } @@ -167,25 +161,53 @@ internal class FirMissingAnnotationsTherapistExtensionSessionComponent( fun shouldAnnotateVariable(declaration: FirVariable): Boolean { val containingClass = declaration.getContainingClass(session) - val should = shouldAnnotate(containingClass?.classId?.packageFqName?.asString() ?: "") + val packageName = containingClass?.classId?.packageFqName?.asString() ?: "" + val should = shouldAnnotate(packageName, PropertyTypeTarget.LOCAL_VARIABLE) if (enableLogging) { println("Checking if should annotate variable ${declaration.name}: $should") } return should } - private fun shouldAnnotate(packageFqName: String): Boolean { + private fun shouldAnnotate(packageFqName: String, targetType: Enum<*>): Boolean { val result = args.annotations.any { annotate -> + // Check Module Target + if (annotate.moduleTarget.isNotEmpty()) { + // Implement module inclusion/exclusion logic if needed + // For now, skipping moduleTarget handling + } + + // Check Package Target annotate.packageTarget.any { packageTarget -> when (packageTarget.matchType) { MatchType.EXACT -> packageFqName == packageTarget.pattern MatchType.WILDCARD -> packageFqName.startsWith(packageTarget.pattern.removeSuffix("*")) MatchType.REGEX -> packageFqName.matches(Regex(packageTarget.regex ?: return@any false)) } + } && when (targetType) { + ClassTypeTarget.REGULAR_CLASS -> annotate.classTargets.contains(ClassTypeTarget.REGULAR_CLASS) + ClassTypeTarget.ENUM_CLASS -> annotate.classTargets.contains(ClassTypeTarget.ENUM_CLASS) + ClassTypeTarget.SEALED_CLASS -> annotate.classTargets.contains(ClassTypeTarget.SEALED_CLASS) + ClassTypeTarget.DATA_CLASS -> annotate.classTargets.contains(ClassTypeTarget.DATA_CLASS) + ClassTypeTarget.OBJECT_CLASS -> annotate.classTargets.contains(ClassTypeTarget.OBJECT_CLASS) + ClassTypeTarget.ANNOTATION_CLASS -> annotate.classTargets.contains(ClassTypeTarget.ANNOTATION_CLASS) + FunctionTypeTarget.FUNCTION -> annotate.functionTargets.contains(FunctionTypeTarget.FUNCTION) + FunctionTypeTarget.SUSPEND_FUNCTION -> annotate.functionTargets.contains(FunctionTypeTarget.SUSPEND_FUNCTION) + FunctionTypeTarget.LAMBDA -> annotate.functionTargets.contains(FunctionTypeTarget.LAMBDA) + FunctionTypeTarget.CONSTRUCTOR -> annotate.functionTargets.contains(FunctionTypeTarget.CONSTRUCTOR) + PropertyTypeTarget.PROPERTY -> annotate.propertyTargets.contains(PropertyTypeTarget.PROPERTY) + PropertyTypeTarget.FIELD -> annotate.propertyTargets.contains(PropertyTypeTarget.FIELD) + PropertyTypeTarget.LOCAL_VARIABLE -> annotate.propertyTargets.contains(PropertyTypeTarget.LOCAL_VARIABLE) + PropertyTypeTarget.VALUE_PARAMETER -> annotate.propertyTargets.contains(PropertyTypeTarget.VALUE_PARAMETER) + PropertyTypeTarget.GETTER -> annotate.propertyTargets.contains(PropertyTypeTarget.GETTER) + PropertyTypeTarget.SETTER -> annotate.propertyTargets.contains(PropertyTypeTarget.SETTER) + TypeAliasTarget.TYPE_ALIAS -> annotate.typeAliasTargets.contains(TypeAliasTarget.TYPE_ALIAS) + FileTarget.FILE -> annotate.fileTargets.contains(FileTarget.FILE) + else -> false } } if (enableLogging) { - println("shouldAnnotate for package '$packageFqName': $result") + println("shouldAnnotate for package '$packageFqName' and target '$targetType': $result") } return result } @@ -234,7 +256,6 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( override val propertyCheckers: Set = setOf(MissingAnnotationsTherapistPropertyChecker(session)) - } class MissingAnnotationsTherapistClassChecker( @@ -280,7 +301,6 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( annotateClassRecursively(propertyClass, session, annotateConfig, isFieldReferenced = true) } } - } @OptIn(SymbolInternals::class) @@ -296,10 +316,10 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( session: FirSession, annotateConfig: Annotate, ) { - val existingAnnotationFqNames = declaration.annotations.map { it.fqName(session)?.asString()?.toClassId() } + val existingAnnotationFqNames = declaration.annotations.mapNotNull { it.fqName(session)?.asString() } val newAnnotations = annotateConfig.annotationsToAdd.filter { annotation -> - val fqName = annotation.toFqName().asString().toClassId() + val fqName = annotation.fqName if (fqName in existingAnnotationFqNames) { if (session.myFirExtensionSessionComponent.enableLogging) { println("Skipping duplicate annotation: $fqName for ${declaration.nameAsString}") @@ -327,7 +347,7 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( } val annotateConfigs = component.args.annotations.filter { annotate -> - annotate.annotationsTarget.contains(AnnotationTarget.CLASS) && + annotate.classTargets.isNotEmpty() && annotate.packageTarget.any { packageTarget -> packageTarget.match(declaration.classId.packageFqName.asString()) } @@ -362,7 +382,7 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( val annotateConfigs = context.session.myFirExtensionSessionComponent.args.annotations .filter { annotate -> - annotate.annotationsTarget.contains(AnnotationTarget.FUNCTION) && + annotate.functionTargets.isNotEmpty() && annotate.packageTarget.any { packageTarget -> when (packageTarget.matchType) { MatchType.EXACT -> declaration.getContainingClass(session)?.classId?.packageFqName?.asString() == packageTarget.pattern @@ -422,7 +442,7 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( val annotateConfigs = context.session.myFirExtensionSessionComponent.args.annotations .filter { annotate -> - annotate.annotationsTarget.contains(AnnotationTarget.PROPERTY) && + annotate.propertyTargets.isNotEmpty() && annotate.packageTarget.any { packageTarget -> when (packageTarget.matchType) { MatchType.EXACT -> declaration.getContainingClass(session)?.classId?.packageFqName?.asString() == packageTarget.pattern @@ -458,10 +478,9 @@ internal class MissingAnnotationsTherapistExtensionFirCheckersExtension( } } } - - } + fun PackageTarget.match(packageName: String): Boolean { return when (matchType) { MatchType.EXACT -> packageName == pattern @@ -470,7 +489,6 @@ fun PackageTarget.match(packageName: String): Boolean { } } - @OptIn(SymbolInternals::class) private fun evaluateConditions( declaration: T, @@ -488,12 +506,10 @@ private fun evaluateConditions( declarationHasAnnotation(declaration, existing, session) } - val absenceCheck = condition.annotationsAbsence.none { absent -> declarationHasAnnotation(declaration, absent, session) } - val namePatternCheck = condition.namePattern?.let { pattern -> val name = declaration.nameAsString val matches = Regex(pattern).matches(name) @@ -503,7 +519,6 @@ private fun evaluateConditions( matches } ?: true - val visibilityCheck = condition.visibility?.let { visibility -> val actualVisibility = declaration.visibilityForApproximation(declaration) val matches = when (visibility) { @@ -518,7 +533,6 @@ private fun evaluateConditions( matches } ?: true - val modifiersCheck = condition.modifiers?.let { requiredModifiers -> val matches = requiredModifiers.all { modifier -> hasModifier(declaration, modifier) @@ -529,24 +543,21 @@ private fun evaluateConditions( matches } ?: true - val inheritanceCheck = condition.inheritance?.let { inheritance -> val superclassFqName = inheritance.superclass?.toClassId() val actualSuperclasses = when (declaration) { is FirRegularClass -> declaration.superConeTypes.map { it } - .map { it.lookupTag.toSymbol(session)?.fir?.classId } - + .mapNotNull { it.lookupTag.toSymbol(session)?.fir?.classId } else -> emptyList() } - val matches = actualSuperclasses.contains(superclassFqName) + val matches = superclassFqName?.let { actualSuperclasses.contains(it) } ?: true if (component.enableLogging) { println("InheritanceCheck for '${declaration.nameAsString}' with superclass '$superclassFqName': $matches") } matches } ?: true - val typeConditionCheck = condition.typeCondition?.let { typeCondition -> val typeNames = typeCondition.typeNames val actualTypeName = when (declaration) { @@ -564,10 +575,8 @@ private fun evaluateConditions( matches } ?: true - val customPredicateCheck = true/*condition.customPredicate?.invoke(declaration) ?: true*/ - val allConditionsMet = existingCheck && absenceCheck && namePatternCheck && @@ -593,7 +602,6 @@ fun Modifier.toKtModifierKeywordToken(): KtModifierKeywordToken = when (this) { Modifier.PRIVATE -> KtTokens.PRIVATE_KEYWORD } - private fun hasModifier(declaration: FirDeclaration, modifier: Modifier): Boolean { return declaration.hasModifier(modifier.toKtModifierKeywordToken()) } @@ -603,36 +611,46 @@ private fun declarationHasAnnotation(declaration: T, fqName: String, session return when (declaration) { is FirRegularClass -> { val has = declaration.hasAnnotation(fqName.toClassId(), session) - println("DeclarationHasAnnotation: Class ${declaration.name} has annotation $fqName: $has") + if (session.myFirExtensionSessionComponent.enableLogging) { + println("DeclarationHasAnnotation: Class ${declaration.name} has annotation $fqName: $has") + } has } is FirSimpleFunction -> { val has = declaration.hasAnnotation(fqName.toClassId(), session) - println("DeclarationHasAnnotation: Function ${declaration.name} has annotation $fqName: $has") + if (session.myFirExtensionSessionComponent.enableLogging) { + println("DeclarationHasAnnotation: Function ${declaration.name} has annotation $fqName: $has") + } has } is FirProperty -> { val has = declaration.hasAnnotation(fqName.toClassId(), session) - println("DeclarationHasAnnotation: Property ${declaration.name} has annotation $fqName: $has") + if (session.myFirExtensionSessionComponent.enableLogging) { + println("DeclarationHasAnnotation: Property ${declaration.name} has annotation $fqName: $has") + } has } is FirVariable -> { val has = declaration.hasAnnotation(fqName.toClassId(), session) - println("DeclarationHasAnnotation: Variable ${declaration.name} has annotation $fqName: $has") + if (session.myFirExtensionSessionComponent.enableLogging) { + println("DeclarationHasAnnotation: Variable ${declaration.name} has annotation $fqName: $has") + } has } else -> { - println("DeclarationHasAnnotation: Unknown declaration type for $declaration") + if (session.myFirExtensionSessionComponent.enableLogging) { + println("DeclarationHasAnnotation: Unknown declaration type for $declaration") + } false } } } -private fun Annotation.toFirAnnotation(session: FirSession, declaration: FirDeclaration): List { +private fun dev.shalaga44.mat.utils.Annotation.toFirAnnotation(session: FirSession, declaration: FirDeclaration): List { return listOf( createFirAnnotation( fqName = this.toFqName(), @@ -644,11 +662,10 @@ private fun Annotation.toFirAnnotation(session: FirSession, declaration: FirDecl ) } -private fun Annotation.toFqName(): FqName { +private fun dev.shalaga44.mat.utils.Annotation.toFqName(): FqName { return FqName(this.fqName) } - fun createFirAnnotation( fqName: FqName, parameters: Map, @@ -665,10 +682,10 @@ fun createFirAnnotation( ) } - val parametersFinal: Map = emptyMap()/*parameters*/ - argumentMapping = parametersFinal.ifEmpty { null }?.let { + val parametersFinal: Map = parameters + argumentMapping = if (parametersFinal.isNotEmpty()) { buildAnnotationArgumentMapping { - it.forEach { (key, value) -> + parametersFinal.forEach { (key, value) -> val processedValue = value.replace("{className}", declarationName) val name = Name.identifier(key) mapping[name] = buildLiteralExpression( @@ -679,13 +696,14 @@ fun createFirAnnotation( ) } } - } ?: FirEmptyAnnotationArgumentMapping + } else { + FirEmptyAnnotationArgumentMapping + } } public inline fun String.toClassId(): ClassId = FqName(this).run { ClassId(parent(), shortName()) } - private fun isNestedClass(declaration: FirRegularClass, session: FirSession): Boolean { val containingClass = declaration.symbol.getContainingClassSymbol(session) return containingClass != null diff --git a/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/utils/MissingAnnotationsTherapistGradleExtension.kt b/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/utils/MissingAnnotationsTherapistGradleExtension.kt index c1149cc..0b8e092 100644 --- a/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/utils/MissingAnnotationsTherapistGradleExtension.kt +++ b/missing-annotations-therapist-plugin/src/main/kotlin/dev/shalaga44/mat/utils/MissingAnnotationsTherapistGradleExtension.kt @@ -40,10 +40,7 @@ data class PackageTarget( val pattern: String, val matchType: MatchType = MatchType.EXACT, val regex: String? = null, -) { - val shortName: String = pattern.substringAfterLast(".") - -} +) /** * Specifies conditions under which annotations should be applied. @@ -52,7 +49,7 @@ data class PackageTarget( * @property annotationsAbsence Annotations that must be absent. * @property visibility Visibility modifier required (PUBLIC, PRIVATE, etc.). * @property modifiers Additional modifiers required (OPEN, FINAL, etc.). - * @property namePattern Regex pattern that the nameAsString must match. + * @property namePattern Regex pattern that the name must match. * @property inheritance Conditions based on superclass and interfaces. * @property typeCondition Conditions based on type names (for properties). * @property customPredicate Custom logic for complex conditions. @@ -71,7 +68,7 @@ data class Condition( /** * Defines inheritance-based conditions. * - * @property superclass Fully qualified nameAsString of the required superclass. + * @property superclass Fully qualified name of the required superclass. * @property interfaces List of fully qualified names of required interfaces. */ data class InheritanceCondition( @@ -129,19 +126,77 @@ enum class Modifier { PRIVATE, } +/** + * Defines different types of class targets for annotations. + */ +enum class ClassTypeTarget { + REGULAR_CLASS, + ENUM_CLASS, + SEALED_CLASS, + DATA_CLASS, + OBJECT_CLASS, + ANNOTATION_CLASS, +} + +/** + * Defines different function types for annotations. + */ +enum class FunctionTypeTarget { + FUNCTION, + SUSPEND_FUNCTION, + LAMBDA, + CONSTRUCTOR, +} + +/** + * Defines different property targets for annotations. + */ +enum class PropertyTypeTarget { + PROPERTY, + FIELD, + LOCAL_VARIABLE, + VALUE_PARAMETER, + GETTER, + SETTER, +} + +/** + * Defines type alias targets for annotations. + */ +enum class TypeAliasTarget { + TYPE_ALIAS, +} + +/** + * Defines file-level targets for annotations. + */ +enum class FileTarget { + FILE, +} + /** * Represents an annotation addition rule. * * @property annotationsToAdd List of annotations to add. - * @property annotationsTarget List of Kotlin annotation targets (CLASS, FUNCTION, PROPERTY, etc.). + * @property classTargets List of class types to apply annotations to. + * @property functionTargets List of function types to apply annotations to. + * @property propertyTargets List of property types to apply annotations to. + * @property typeAliasTargets List of type alias targets. + * @property fileTargets List of file targets. * @property packageTarget List of package targets where annotations should be applied. * @property moduleTarget List of module targets to include or exclude. * @property sourceSets List of source sets to apply the annotations to. * @property conditions List of conditions that must be met to apply the annotations. + * @property annotateNestedClasses If true, nested classes will also be annotated. + * @property annotateFieldClasses If true, field-referenced classes will also be annotated. */ data class Annotate( var annotationsToAdd: List, - var annotationsTarget: List = listOf(), + var classTargets: List = listOf(), + var functionTargets: List = listOf(), + var propertyTargets: List = listOf(), + var typeAliasTargets: List = listOf(), + var fileTargets: List = listOf(), var packageTarget: List, var moduleTarget: List = listOf(), var sourceSets: List = listOf(), @@ -153,6 +208,6 @@ data class Annotate( /** * Gradle extension for configuring the MissingAnnotationsTherapist compiler plugin. */ -open class MissingAnnotationsTherapistGradleExtension { +open class MissingAnnotationsTherapist { var annotations: List = listOf() } diff --git a/missing-annotations-therapist-plugin/src/test/kotlin/dev/shalaga44/mat/MatTests.kt b/missing-annotations-therapist-plugin/src/test/kotlin/dev/shalaga44/mat/MatTests.kt index 86fe5e4..6500c50 100644 --- a/missing-annotations-therapist-plugin/src/test/kotlin/dev/shalaga44/mat/MatTests.kt +++ b/missing-annotations-therapist-plugin/src/test/kotlin/dev/shalaga44/mat/MatTests.kt @@ -1,21 +1,12 @@ package dev.shalaga44.mat import com.tschuchort.compiletesting.SourceFile -import dev.shalaga44.mat.utils.Annotate -import dev.shalaga44.mat.utils.Annotation -import dev.shalaga44.mat.utils.Condition -import dev.shalaga44.mat.utils.InheritanceCondition -import dev.shalaga44.mat.utils.MatchType -import dev.shalaga44.mat.utils.Modifier -import dev.shalaga44.mat.utils.PackageTarget -import dev.shalaga44.mat.utils.TypeCondition -import dev.shalaga44.mat.utils.Visibility +import dev.shalaga44.mat.utils.* import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class MatTests { - private fun annotationFile( fileName: String, packageName: String = "com.project", @@ -30,10 +21,10 @@ class MatTests { return SourceFile.kotlin( fileName, """ - package $packageName + package $packageName - annotation class $annotationName$params - """.trimIndent(), + annotation class $annotationName$params + """.trimIndent(), ) } @@ -42,17 +33,17 @@ class MatTests { val myDto = annotationFile("MyDto.kt", "com.project", "MyDto") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class Hello + class Hello - fun main() { - val hello = Hello() - val annotations = hello::class.annotations - assertTrue(annotations.any { it.annotationClass.simpleName == "MyDto" }) - } - """.trimIndent() + fun main() { + val hello = Hello() + val annotations = hello::class.annotations + assertTrue(annotations.any { it.annotationClass.simpleName == "MyDto" }) + } + """.trimIndent() run( mainSource = mainSource, @@ -64,8 +55,8 @@ class MatTests { annotations = listOf( Annotate( annotationsToAdd = listOf(Annotation(fqName = "com.project.MyDto")), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf(PackageTarget(pattern = "com.project")), - annotationsTarget = listOf(AnnotationTarget.CLASS), ), ), ), @@ -80,17 +71,17 @@ class MatTests { val serializable = annotationFile("Serializable.kt", "kotlinx.serialization", "Serializable") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class Hello + class Hello - fun main() { - val hello = Hello() - val annotations = hello::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.containsAll(listOf("MyDto", "Serializable"))) - } - """.trimIndent() + fun main() { + val hello = Hello() + val annotations = hello::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.containsAll(listOf("MyDto", "Serializable"))) + } + """.trimIndent() run( mainSource = mainSource, @@ -105,8 +96,8 @@ class MatTests { Annotation(fqName = "com.project.MyDto"), Annotation(fqName = "kotlinx.serialization.Serializable"), ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf(PackageTarget(pattern = "com.project")), - annotationsTarget = listOf(AnnotationTarget.CLASS), ), ), ), @@ -120,18 +111,18 @@ class MatTests { val myFunctionAnnotation = annotationFile("MyFunctionAnnotation.kt", "com.project", "MyFunctionAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class Hello { - fun greet() {} - } + class Hello { + fun greet() {} + } - fun main() { - val greetAnnotations = Hello::greet.annotations.map { it.annotationClass.simpleName } - assertTrue(greetAnnotations.contains("MyFunctionAnnotation")) - } - """.trimIndent() + fun main() { + val greetAnnotations = Hello::greet.annotations.map { it.annotationClass.simpleName } + assertTrue(greetAnnotations.contains("MyFunctionAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -145,9 +136,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.MyFunctionAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.FUNCTION, - ), + functionTargets = listOf(FunctionTypeTarget.FUNCTION), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -164,20 +153,20 @@ class MatTests { val propertyValidation = annotationFile("PropertyValidation.kt", "com.project", "PropertyValidation") val mainSource = """ - package com.project - import kotlin.test.assertTrue - import kotlin.reflect.full.memberProperties + package com.project + import kotlin.test.assertTrue + import kotlin.reflect.full.memberProperties - class User { - private val isActive: Boolean = true - } + class User { + private val isActive: Boolean = true + } - fun main() { - val user = User() - val annotations = user::class.memberProperties.find { it.name == "isActive" }?.annotations - assertTrue(annotations?.any { it.annotationClass.simpleName == "PropertyValidation" } == true) - } - """.trimIndent() + fun main() { + val user = User() + val annotations = user::class.memberProperties.find { it.name == "isActive" }?.annotations + assertTrue(annotations?.any { it.annotationClass.simpleName == "PropertyValidation" } == true) + } + """.trimIndent() run( mainSource = mainSource, @@ -191,9 +180,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.PropertyValidation"), ), - annotationsTarget = listOf( - AnnotationTarget.PROPERTY, - ), + propertyTargets = listOf(PropertyTypeTarget.PROPERTY), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -219,16 +206,16 @@ class MatTests { val serviceAnnotation = annotationFile("ServiceAnnotation.kt", "com.project", "ServiceAnnotation") val mainSource = """ - package com.project.service - import kotlin.test.assertTrue + package com.project.service + import kotlin.test.assertTrue - class ServiceClass + class ServiceClass - fun main() { - val annotations = ServiceClass::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.contains("ServiceAnnotation")) - } - """.trimIndent() + fun main() { + val annotations = ServiceClass::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.contains("ServiceAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -242,11 +229,12 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.ServiceAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( - PackageTarget(pattern = "com.project.*", matchType = MatchType.WILDCARD), + PackageTarget( + pattern = "com.project.*", + matchType = MatchType.WILDCARD + ), ), ), ), @@ -261,16 +249,16 @@ class MatTests { val dataModelAnnotation = annotationFile("DataModelAnnotation.kt", "com.project", "DataModelAnnotation") val mainSource = """ - package com.project.data.model - import kotlin.test.assertTrue + package com.project.data.model + import kotlin.test.assertTrue - class DataModel + class DataModel - fun main() { - val annotations = DataModel::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.contains("DataModelAnnotation")) - } - """.trimIndent() + fun main() { + val annotations = DataModel::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.contains("DataModelAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -284,9 +272,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.DataModelAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget( pattern = "com.project.data.model", @@ -308,18 +294,18 @@ class MatTests { val newAnnotation = annotationFile("NewAnnotation.kt", "com.project", "NewAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - @ExistingAnnotation - class ExistingAnnotatedClass + @ExistingAnnotation + class ExistingAnnotatedClass - fun main() { - val annotations = ExistingAnnotatedClass::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.contains("ExistingAnnotation")) - assertTrue(annotations.contains("NewAnnotation")) - } - """.trimIndent() + fun main() { + val annotations = ExistingAnnotatedClass::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.contains("ExistingAnnotation")) + assertTrue(annotations.contains("NewAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -333,9 +319,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.NewAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -359,18 +343,18 @@ class MatTests { annotationFile("DynamicAnnotation.kt", "com.project", "DynamicAnnotation", "val exportName: String") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class DynamicClass + class DynamicClass - fun main() { - val annotations = DynamicClass::class.annotations.find { it.annotationClass.simpleName == "DynamicAnnotation" } - assertTrue(annotations != null) - val exportName = annotations?.annotationClass?.qualifiedName - assertTrue(exportName?.contains("DynamicClass") == true) - } - """.trimIndent() + fun main() { + val annotations = DynamicClass::class.annotations.find { it.annotationClass.simpleName == "DynamicAnnotation" } + assertTrue(annotations != null) + val exportName = annotations?.annotationClass?.qualifiedName + assertTrue(exportName?.contains("DynamicClass") == true) + } + """.trimIndent() run( mainSource = mainSource, @@ -387,9 +371,7 @@ class MatTests { parameters = mapOf("exportName" to "{className}"), ), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -406,18 +388,18 @@ class MatTests { val nestedClassAnnotation = annotationFile("NestedClassAnnotation.kt", "com.project", "NestedClassAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class Outer { - class Inner - } + class Outer { + class Inner + } - fun main() { - val annotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.contains("NestedClassAnnotation")) - } - """.trimIndent() + fun main() { + val annotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.contains("NestedClassAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -428,10 +410,10 @@ class MatTests { MissingAnnotationsTherapistArgs( annotations = listOf( Annotate( - annotationsToAdd = listOf(Annotation(fqName = "com.project.NestedClassAnnotation")), - annotationsTarget = listOf(AnnotationTarget.CLASS), - packageTarget = listOf(PackageTarget(pattern = "com.project")), - annotateNestedClasses = true, + annotationsToAdd = listOf(Annotation(fqName = "com.project.NestedClassAnnotation")), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), + packageTarget = listOf(PackageTarget(pattern = "com.project")), + annotateNestedClasses = true, ), ), ), @@ -446,29 +428,29 @@ class MatTests { val baseClass = SourceFile.kotlin( "BaseClass.kt", """ - package com.project + package com.project - open class BaseClass - """.trimIndent(), + open class BaseClass + """.trimIndent(), ) val derivedClass = SourceFile.kotlin( "DerivedClass.kt", """ - package com.project + package com.project - class DerivedClass : BaseClass() - """.trimIndent(), + class DerivedClass : BaseClass() + """.trimIndent(), ) val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - fun main() { - val annotations = DerivedClass::class.annotations.map { it.annotationClass.simpleName } - assertTrue(annotations.contains("InheritedAnnotation")) - } - """.trimIndent() + fun main() { + val annotations = DerivedClass::class.annotations.map { it.annotationClass.simpleName } + assertTrue(annotations.contains("InheritedAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -482,9 +464,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.InheritedAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -508,22 +488,22 @@ class MatTests { val openFunctionAnnotation = annotationFile("OpenFunctionAnnotation.kt", "com.project", "OpenFunctionAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue - - class Service { - open fun perform() {} - fun execute() {} - } - - fun main() { - val service = Service() - val openAnnotations = service::perform.annotations.map { it.annotationClass.simpleName } - val executeAnnotations = service::execute.annotations.map { it.annotationClass.simpleName } - assertTrue(openAnnotations.contains("OpenFunctionAnnotation")) - assertTrue(!executeAnnotations.contains("OpenFunctionAnnotation")) - } - """.trimIndent() + package com.project + import kotlin.test.assertTrue + + class Service { + open fun perform() {} + fun execute() {} + } + + fun main() { + val service = Service() + val openAnnotations = service::perform.annotations.map { it.annotationClass.simpleName } + val executeAnnotations = service::execute.annotations.map { it.annotationClass.simpleName } + assertTrue(openAnnotations.contains("OpenFunctionAnnotation")) + assertTrue(!executeAnnotations.contains("OpenFunctionAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -537,9 +517,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.OpenFunctionAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.FUNCTION, - ), + functionTargets = listOf(FunctionTypeTarget.FUNCTION), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -561,18 +539,19 @@ class MatTests { val localVarAnnotation = annotationFile("LocalVarAnnotation.kt", "com.project", "LocalVarAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue - - fun main() { - val tempValue = 42 - // Simulating local variable annotation retrieval - // Since Kotlin does not support runtime reflection on local variables, we use an approach to attach annotations manually. - // This is for illustrative purposes and may not work as expected in a real-world scenario. - val annotations = listOf(LocalVarAnnotation()) - assertTrue(annotations.any { it.annotationClass.simpleName == "LocalVarAnnotation" }) - } - """.trimIndent() + package com.project + import kotlin.test.assertTrue + + fun main() { + @LocalVarAnnotation + val tempValue = 42 + // Simulating local variable annotation retrieval + // Since Kotlin does not support runtime reflection on local variables, we use an approach to attach annotations manually. + // This is for illustrative purposes and may not work as expected in a real-world scenario. + val annotations = listOf(LocalVarAnnotation()) + assertTrue(annotations.any { it.annotationClass.simpleName == "LocalVarAnnotation" }) + } + """.trimIndent() run( mainSource = mainSource, @@ -586,9 +565,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.LocalVarAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.LOCAL_VARIABLE, - ), + propertyTargets = listOf(PropertyTypeTarget.LOCAL_VARIABLE), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -635,9 +612,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.UniqueAnnotation"), ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, - ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget(pattern = "com.project"), ), @@ -651,7 +626,7 @@ class MatTests { @Test fun `add annotation to class in specific module`() { - val moduleAAnnotation = annotationFile("ModuleAAnnotation.kt", "com.project", "ModuleAAnnotation") + val moduleAAnnotation = annotationFile("ModuleAAnnotation.kt", "com.project.moduleA", "ModuleAAnnotation") val mainSource = """ package com.project.moduleA @@ -675,14 +650,13 @@ class MatTests { annotations = listOf( Annotate( annotationsToAdd = listOf( - Annotation(fqName = "com.project.ModuleAAnnotation"), - ), - annotationsTarget = listOf( - AnnotationTarget.CLASS, + Annotation(fqName = "com.project.moduleA.ModuleAAnnotation"), ), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf( PackageTarget(pattern = "com.project.moduleA"), ), + // Implement moduleTarget handling if required ), ), ), @@ -696,22 +670,22 @@ class MatTests { val annotation = annotationFile("TestAnnotation.kt", "com.project", "TestAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue + package com.project + import kotlin.test.assertTrue - class Outer { - class Inner - val field: Inner = Inner() - } + class Outer { + class Inner + val field: Inner = Inner() + } - fun main() { - val innerAnnotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } - val fieldAnnotations = Outer().field::class.annotations.map { it.annotationClass.simpleName } + fun main() { + val innerAnnotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } + val fieldAnnotations = Outer().field::class.annotations.map { it.annotationClass.simpleName } - assertTrue(innerAnnotations.contains("TestAnnotation")) - assertTrue(fieldAnnotations.contains("TestAnnotation")) - } - """.trimIndent() + assertTrue(innerAnnotations.contains("TestAnnotation")) + assertTrue(fieldAnnotations.contains("TestAnnotation")) + } + """.trimIndent() run( mainSource = mainSource, @@ -725,7 +699,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.TestAnnotation"), ), - annotationsTarget = listOf(AnnotationTarget.CLASS), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf(PackageTarget(pattern = "com.project")), annotateNestedClasses = true, annotateFieldClasses = true, @@ -742,22 +716,22 @@ class MatTests { val annotation = annotationFile("TestAnnotation.kt", "com.project", "TestAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertTrue - - class Outer { - class Inner - val field: Inner = Inner() - } - - fun main() { - val innerAnnotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } - val fieldAnnotations = Outer().field::class.annotations.map { it.annotationClass.simpleName } - - assertTrue(!innerAnnotations.contains("TestAnnotation"), "innerAnnotations must not be annotated") - assertTrue(!fieldAnnotations.contains("TestAnnotation"), "fieldAnnotations must not be annotated") - } - """.trimIndent() + package com.project + import kotlin.test.assertTrue + + class Outer { + class Inner + val field: Inner = Inner() + } + + fun main() { + val innerAnnotations = Outer.Inner::class.annotations.map { it.annotationClass.simpleName } + val fieldAnnotations = Outer().field::class.annotations.map { it.annotationClass.simpleName } + + assertTrue(!innerAnnotations.contains("TestAnnotation"), "innerAnnotations must not be annotated") + assertTrue(!fieldAnnotations.contains("TestAnnotation"), "fieldAnnotations must not be annotated") + } + """.trimIndent() run( mainSource = mainSource, @@ -771,7 +745,7 @@ class MatTests { annotationsToAdd = listOf( Annotation(fqName = "com.project.TestAnnotation"), ), - annotationsTarget = listOf(AnnotationTarget.CLASS), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf(PackageTarget(pattern = "com.project")), annotateNestedClasses = false, annotateFieldClasses = false, @@ -788,38 +762,37 @@ class MatTests { val annotation = annotationFile("TestAnnotation.kt", "com.project", "TestAnnotation") val mainSource = """ - package com.project - import kotlin.test.assertEquals + package com.project + import kotlin.test.assertEquals - class Outer { - class Inner - } + class Outer { + class Inner + } - fun main() { - val annotations = Outer.Inner::class.annotations.filter { it.annotationClass.simpleName == "TestAnnotation" } - assertEquals(1, annotations.size) - } - """.trimIndent() + fun main() { + val annotations = Outer.Inner::class.annotations.filter { it.annotationClass.simpleName == "TestAnnotation" } + assertEquals(1, annotations.size) + } + """.trimIndent() run( - mainSource = mainSource, - additionalSources = listOf(annotation), - mainApplication = "com.project.MainKt", - compilerPluginRegistrars = arrayOf( - MissingAnnotationsTherapistCompilerPluginRegistrar( - MissingAnnotationsTherapistArgs( - annotations = listOf( - Annotate( - annotationsToAdd = listOf(Annotation(fqName = "com.project.TestAnnotation")), - annotationsTarget = listOf(AnnotationTarget.CLASS), - packageTarget = listOf(PackageTarget(pattern = "com.project")), - annotateNestedClasses = true, - ), - ), - ), + mainSource = mainSource, + additionalSources = listOf(annotation), + mainApplication = "com.project.MainKt", + compilerPluginRegistrars = arrayOf( + MissingAnnotationsTherapistCompilerPluginRegistrar( + MissingAnnotationsTherapistArgs( + annotations = listOf( + Annotate( + annotationsToAdd = listOf(Annotation(fqName = "com.project.TestAnnotation")), + classTargets = listOf(ClassTypeTarget.REGULAR_CLASS), + packageTarget = listOf(PackageTarget(pattern = "com.project")), + annotateNestedClasses = true, + ), ), + ), ), + ), ) } } - diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 4e35574..e7bb6bd 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,7 +1,7 @@ import dev.shalaga44.mat.* plugins { - id("io.github.shalaga44.missing-annotations-therapist") version "0.0.1" + id("io.github.shalaga44.missing-annotations-therapist") version "0.1.0" kotlin("multiplatform") version "2.0.21" } @@ -83,7 +83,7 @@ configure { annotations = listOf( Annotate( annotationsToAdd = listOf(Annotation("kotlin.js.JsExport")), - annotationsTarget = listOf(AnnotationTarget.CLASS), + classesclassTargets = listOf(ClassTypeTarget.REGULAR_CLASS), packageTarget = listOf(PackageTarget("com.project.common.dto.js")), sourceSets = listOf( "jsMain", "jsTest", ),