Skip to content

Commit

Permalink
Fir Class checker
Browse files Browse the repository at this point in the history
  • Loading branch information
erwin-kok committed Dec 21, 2024
1 parent 116f12a commit 4f4cba7
Show file tree
Hide file tree
Showing 16 changed files with 421 additions and 11 deletions.
2 changes: 1 addition & 1 deletion build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repositories {

dependencies {
implementation(libs.kotlin.gradlePlugin)

implementation(libs.testlogger.gradlePlugin)
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}

Expand Down
6 changes: 6 additions & 0 deletions build-logic/src/main/kotlin/kik.common.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import com.adarshr.gradle.testlogger.theme.ThemeType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
idea
kotlin("jvm")
com.adarshr.`test-logger`
}

group = "org.erwinkok.kik"
version = "0.1.0"

testlogger {
theme = ThemeType.MOCHA
}

tasks.test {
useJUnitPlatform()
}
Expand Down
8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ tasks.withType<DependencyUpdatesTask> {
isNonStable(candidate.version)
}
}

tasks.register("build") {
dependsOn(gradle.includedBuild("kik-compiler-plugin").task(":build"))
}

tasks.register("clean") {
dependsOn(gradle.includedBuild("kik-compiler-plugin").task(":clean"))
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ kotlin-compile-testing = "0.7.0"
# Plugins
compatibility-plugin = "0.16.3"
ksp-plugin = "2.1.0-1.0.29"
testlogger-plugin = "4.0.0"
versions-plugin = "0.51.0"

[libraries]
kotlin-compiler-embeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
testlogger-gradlePlugin = { module = "com.adarshr:gradle-test-logger-plugin", version.ref = "testlogger-plugin" }

auto-service = { module = "com.google.auto.service:auto-service", version.ref = "auto-service" }
auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "auto-service" }
Expand All @@ -22,5 +24,6 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl
[plugins]
compatibility = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "compatibility-plugin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-plugin" }
testlogger = { id = "com.adarshr.test-logger", version.ref = "testlogger-plugin" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions-plugin" }

14 changes: 14 additions & 0 deletions kik-compiler-plugin/api/kik-compiler-plugin.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
public final class org/erwinkok/kik/compiler/KikCommandLineProcessor : org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor {
public fun <init> ()V
public fun getPluginId ()Ljava/lang/String;
public synthetic fun getPluginOptions ()Ljava/util/Collection;
public fun getPluginOptions ()Ljava/util/List;
public fun processOption (Lorg/jetbrains/kotlin/compiler/plugin/AbstractCliOption;Ljava/lang/String;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V
}

public final class org/erwinkok/kik/compiler/SerializationComponentRegistrar : org/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar {
public fun <init> ()V
public fun getSupportsK2 ()Z
public fun registerExtensions (Lorg/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar$ExtensionStorage;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V
}

10 changes: 10 additions & 0 deletions kik-compiler-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import com.adarshr.gradle.testlogger.theme.ThemeType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

Expand All @@ -6,6 +7,7 @@ plugins {
kotlin("jvm") version "2.1.0"
alias(libs.plugins.ksp)
alias(libs.plugins.compatibility)
alias(libs.plugins.testlogger)
}

group = "org.erwinkok.kik"
Expand All @@ -27,6 +29,14 @@ dependencies {
testImplementation(libs.kotlin.test)
}

testlogger {
theme = ThemeType.MOCHA
}

tasks.test {
useJUnitPlatform()
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ import kotlin.text.toBoolean

@AutoService(CommandLineProcessor::class)
class KikCommandLineProcessor : CommandLineProcessor {
companion object {
override val pluginId = "org.erwinkok.kik.kik-compiler-plugin"
override val pluginOptions = listOf(ENABLED_OPTION, DEBUG_OPTION)

override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) {
ENABLED_OPTION -> configuration.put(KEY_ENABLED, value.toBoolean())
DEBUG_OPTION -> configuration.put(KEY_DEBUG, value.toBoolean())
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}

internal companion object {
internal val KEY_ENABLED = CompilerConfigurationKey<Boolean>("Enable/disable the kik compiler plugin on the given compilation")
internal val KEY_DEBUG = CompilerConfigurationKey<Boolean>("Enable/disable debug logging on the given compilation")

Expand All @@ -35,13 +44,4 @@ class KikCommandLineProcessor : CommandLineProcessor {
allowMultipleOccurrences = false,
)
}

override val pluginId = "org.erwinkok.kik.kik-compiler-plugin"
override val pluginOptions = listOf(ENABLED_OPTION, DEBUG_OPTION)

override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) {
ENABLED_OPTION -> configuration.put(KEY_ENABLED, value.toBoolean())
DEBUG_OPTION -> configuration.put(KEY_DEBUG, value.toBoolean())
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ package org.erwinkok.kik.compiler

import com.google.auto.service.AutoService
import org.erwinkok.kik.compiler.KikCommandLineProcessor.Companion.KEY_ENABLED
import org.erwinkok.kik.compiler.k2.FirKikExtensionRegistrar
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar.ExtensionStorage
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter

@AutoService(CompilerPluginRegistrar::class)
class SerializationComponentRegistrar : CompilerPluginRegistrar() {
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
if (configuration[KEY_ENABLED] == false) {
return
}
FirExtensionRegistrarAdapter.registerExtension(FirKikExtensionRegistrar())
}

override val supportsK2: Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2024. Erwin Kok. Apache License. See LICENSE file for more details.
package org.erwinkok.kik.compiler.k2

import org.erwinkok.kik.compiler.resolve.KikAnnotations
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol

internal fun FirClassSymbol<*>.hasKikAnnotation(session: FirSession): Boolean {
return serializableAnnotationWithoutArgs(session) != null
}

internal fun FirBasedSymbol<*>.serializableAnnotationWithoutArgs(session: FirSession): FirAnnotation? {
return resolvedCompilerAnnotationsWithClassIds.serializableAnnotation(session)
}

internal fun List<FirAnnotation>.serializableAnnotation(session: FirSession): FirAnnotation? {
return getAnnotationByClassId(KikAnnotations.kikTypeAnnotationClassId, session)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2024. Erwin Kok. Apache License. See LICENSE file for more details.
package org.erwinkok.kik.compiler.k2

import org.erwinkok.kik.compiler.k2.checkers.FirKikCheckersComponent
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar

internal class FirKikExtensionRegistrar : FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::FirKikCheckersComponent
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.erwinkok.kik.compiler.k2.checkers

import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension

internal class FirKikCheckersComponent(session: FirSession) : FirAdditionalCheckersExtension(session) {
override val declarationCheckers = object : DeclarationCheckers() {
override val classCheckers = setOf(FirKikPluginClassChecker)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2024. Erwin Kok. Apache License. See LICENSE file for more details.
package org.erwinkok.kik.compiler.k2.checkers

import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.error0
import org.jetbrains.kotlin.diagnostics.error1
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.psi.KtAnnotationEntry

internal object FirKikErrors : BaseDiagnosticRendererFactory() {
val SUPERCLASS_NOT_SUPPORTED by error1<KtAnnotationEntry, String>()
val OBJECTS_NOT_SUPPORTED by error0<PsiElement>()
val ANONYMOUS_OBJECTS_NOT_SUPPORTED by error0<PsiElement>()
val INNER_CLASSES_NOT_SUPPORTED by error0<PsiElement>()
val TYPE_PARAMETERS_NOT_SUPPORTED by error1<KtAnnotationEntry, String>()

override val MAP = KtDiagnosticFactoryToRendererMap("KikTypeSystem").apply {
put(
SUPERCLASS_NOT_SUPPORTED,
"Class tagged with @KikType has one or more super classes/interfaces ''{0}'', which is not supported",
CommonRenderers.STRING
)
put(
OBJECTS_NOT_SUPPORTED,
"Objects can not be annotated with @KikType."
)
put(
ANONYMOUS_OBJECTS_NOT_SUPPORTED,
"Anonymous classes or contained in its classes can not be annotation with @KikType."
)
put(
INNER_CLASSES_NOT_SUPPORTED,
"Inner (with reference to outer this) classes cannot be annotated with @KikType. Remove 'inner' keyword."
)
put(
TYPE_PARAMETERS_NOT_SUPPORTED,
"Class annotated with @KikType has one or more type parameters ''{0}'', which is not supported",
CommonRenderers.STRING
)
}

init {
RootDiagnosticRendererFactory.registerFactory(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2024. Erwin Kok. Apache License. See LICENSE file for more details.
package org.erwinkok.kik.compiler.k2.checkers

import org.erwinkok.kik.compiler.k2.hasKikAnnotation
import org.jetbrains.kotlin.descriptors.isObject
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
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.FirClassChecker
import org.jetbrains.kotlin.fir.declarations.FirAnonymousObject
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.utils.isInner
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.name.StandardClassIds

internal object FirKikPluginClassChecker : FirClassChecker(MppCheckerKind.Common) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.symbol.hasKikAnnotation(context.session)) {
return
}
val checkers = listOf(
::checkSuperClass,
::checkObject,
::checkAnonymousClass,
::checkInnerClass,
::checkTypeParameters,
)
checkers.forEach { checker ->
if (checker(declaration, context, reporter)) {
return
}
}
}

private fun checkSuperClass(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
val classSymbol = declaration.symbol
val superClasses = classSymbol.resolvedSuperTypes
.mapNotNull {
it.classId
}
.filter {
it != StandardClassIds.Any && it != StandardClassIds.Enum
}
if (superClasses.isNotEmpty()) {
val identifiers = superClasses.joinToString(", ") { it.asFqNameString() }
reporter.reportOn(classSymbol.source, FirKikErrors.SUPERCLASS_NOT_SUPPORTED, identifiers, context)
return true
}
return false
}

private fun checkObject(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
val classSymbol = declaration.symbol
if (classSymbol.classKind.isObject) {
reporter.reportOn(declaration.source, FirKikErrors.OBJECTS_NOT_SUPPORTED, context)
return true
}
return false
}

private fun checkAnonymousClass(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
val classSymbol = declaration.symbol
if (classSymbol is FirAnonymousObjectSymbol || context.containingDeclarations.any { it is FirAnonymousObject }) {
reporter.reportOn(declaration.source, FirKikErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED, context)
return true
}
return false
}

private fun checkInnerClass(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
val classSymbol = declaration.symbol
if (classSymbol.isInner) {
reporter.reportOn(declaration.source, FirKikErrors.INNER_CLASSES_NOT_SUPPORTED, context)
return true
}
return false
}

private fun checkTypeParameters(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
if (declaration.typeParameters.isNotEmpty()) {
val identifiers = declaration.typeParameters.joinToString(", ") { it.symbol.name.identifier }
reporter.reportOn(declaration.source, FirKikErrors.TYPE_PARAMETERS_NOT_SUPPORTED, identifiers, context)
return true
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2024. Erwin Kok. Apache License. See LICENSE file for more details.
package org.erwinkok.kik.compiler.resolve

import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName

internal object KikAnnotations {
val kikTypeAnnotationFqName = FqName("org.erwinkok.kik.typesystem.KikType")
val kikTypePartAnnotationFqName = FqName("org.erwinkok.kik.typesystem.KikTypePart")
val kikPropertyAnnotationFqName = FqName("org.erwinkok.kik.typesystem.KikProperty")
val kikInlineAnnotationFqName = FqName("org.erwinkok.kik.typesystem.KikInline")

val kikTypeAnnotationClassId = ClassId.topLevel(kikTypeAnnotationFqName)
val kikTypePartAnnotationClassId = ClassId.topLevel(kikTypePartAnnotationFqName)
val kikPropertyAnnotationClassId = ClassId.topLevel(kikPropertyAnnotationFqName)
val kikInlineAnnotationClassId = ClassId.topLevel(kikInlineAnnotationFqName)
}
Loading

0 comments on commit 4f4cba7

Please sign in to comment.