[K/JS] Prepare JS Plain Objects plugin to publication

This commit is contained in:
Artem Kobzar
2024-01-17 10:52:58 +00:00
committed by Space Team
parent 561be747c1
commit dfe2d8651e
53 changed files with 479 additions and 393 deletions
@@ -0,0 +1,34 @@
description = "Kotlin JavaScript Plain Objects Compiler Plugin (K2)"
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compileOnly(project(":compiler:fir:cones"))
compileOnly(project(":compiler:fir:tree"))
compileOnly(project(":compiler:fir:resolve"))
compileOnly(project(":compiler:fir:plugin-utils"))
compileOnly(project(":compiler:fir:entrypoint"))
compileOnly(project(":compiler:cli-common"))
implementation(project(":plugins:js-plain-objects:compiler-plugin:js-plain-objects.common"))
compileOnly(intellijCore())
testApi(project(":compiler:fir:plugin-utils"))
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
runtimeJar()
sourcesJar()
javadocJar()
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.add("-Xcontext-receivers")
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlinx.jspo.compiler.fir.checkers.FirJsPlainObjectsCheckersComponent
import org.jetbrains.kotlinx.jspo.compiler.fir.services.JsPlainObjectsPropertiesProvider
class JsPlainObjectsExtensionRegistrar : FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::FirJsPlainObjectsCheckersComponent
+::JsPlainObjectsFunctionsGenerator
// services
+::JsPlainObjectsPropertiesProvider
}
}
@@ -0,0 +1,249 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirFunctionTarget
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.builder.buildRegularClass
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunction
import org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
import org.jetbrains.kotlin.fir.declarations.origin
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.builder.*
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext
import org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.references.builder.buildImplicitThisReference
import org.jetbrains.kotlin.fir.references.builder.buildResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.toEffectiveVisibility
import org.jetbrains.kotlin.fir.types.isNullable
import org.jetbrains.kotlin.fir.types.toFirResolvedTypeRef
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlinx.jspo.compiler.fir.services.jsPlainObjectPropertiesProvider
import org.jetbrains.kotlinx.jspo.compiler.resolve.JsPlainObjectsPluginKey
/**
* The extension generate a synthetic factory and copy-method for an `external interface` annotated with @JsPlainObjects
* Imagine the next interfaces:
* ```
* external interface User {
* val name: String
* }
* @JsPlainObjects
* external interface Admin {
* val chat: Chat
* }
* ```
*
* For the interface `Admin` this function should generate the companion inline function:
* ```
* external interface Admin {
* val chat: Chat
*
* inline fun copy(chat: Chat = this.chat, name: String = this.name): Admin =
* Admin.Companion.invoke(chat, name)
*
* companion object {
* inline operator fun invoke(chat: Chat, name: String): Admin =
* js("{ chat: chat, name: name }")
* }
* }
* ```
*/
class JsPlainObjectsFunctionsGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {
private val predicateBasedProvider = session.predicateBasedProvider
private val matchedInterfaces by lazy {
predicateBasedProvider.getSymbolsByPredicate(JsPlainObjectsPredicates.AnnotatedWithJsPlainObject.LOOKUP)
.filterIsInstance<FirRegularClassSymbol>()
.toSet()
}
private val factoryFqNamesToJsPlainObjectsInterface by lazy {
matchedInterfaces.associateBy { it.classId.asSingleFqName() }
}
private val FirClassLikeSymbol<*>.isJsPlainObject: Boolean
get() = this is FirRegularClassSymbol && this in matchedInterfaces
override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>, context: NestedClassGenerationContext): Set<Name> {
return if (classSymbol.isJsPlainObject) setOf(SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) else emptySet()
}
override fun generateNestedClassLikeDeclaration(
owner: FirClassSymbol<*>,
name: Name,
context: NestedClassGenerationContext
): FirClassLikeSymbol<*>? {
return if (
owner is FirRegularClassSymbol &&
owner.isJsPlainObject &&
name == org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
) generateCompanionDeclaration(owner)
else null
}
private fun generateCompanionDeclaration(owner: FirRegularClassSymbol): FirRegularClassSymbol? {
if (owner.companionObjectSymbol != null) return null
val classId = owner.classId.createNestedClassId(SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT)
return buildRegularClass {
resolvePhase = FirResolvePhase.BODY_RESOLVE
moduleData = session.moduleData
origin = JsPlainObjectsPluginKey.origin
classKind = ClassKind.OBJECT
scopeProvider = session.kotlinScopeProvider
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
Visibilities.Public.toEffectiveVisibility(owner, forClass = true)
).apply {
isExternal = true
isCompanion = true
}
name = classId.shortClassName
symbol = FirRegularClassSymbol(classId)
}.symbol
}
override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>, context: MemberGenerationContext): Set<Name> {
val outerClass = classSymbol.getContainingClassSymbol(session)
return when {
classSymbol.isCompanion && outerClass?.isJsPlainObject == true -> setOf(OperatorNameConventions.INVOKE)
classSymbol.isJsPlainObject -> setOf(StandardNames.DATA_CLASS_COPY)
else -> emptySet()
}
}
override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List<FirNamedFunctionSymbol> {
if (context == null) return emptyList()
val containingClass = callableId.classId
val possibleInterface = containingClass?.outerClassId
return when (callableId.callableName) {
StandardNames.DATA_CLASS_COPY -> {
containingClass
?.let { factoryFqNamesToJsPlainObjectsInterface[it.asSingleFqName()] }
?.let { listOf(createJsPlainObjectCopyFunction(callableId, context.owner, it).symbol) } ?: emptyList()
}
OperatorNameConventions.INVOKE -> {
possibleInterface
?.takeIf { context.owner.isCompanion }
?.let { factoryFqNamesToJsPlainObjectsInterface[it.asSingleFqName()] }
?.let { listOf(createJsPlainObjectFactoryFunction(callableId, context.owner, it).symbol) } ?: emptyList()
}
else -> emptyList()
}
}
private fun createJsPlainObjectFactoryFunction(
callableId: CallableId,
parent: FirClassSymbol<*>,
jsPlainObjectInterface: FirRegularClassSymbol,
): FirSimpleFunction {
return createJsPlainObjectsFunction(callableId, parent, jsPlainObjectInterface) {
runIf(resolvedReturnTypeRef.type.isNullable) {
buildConstExpression(
source = null,
value = null,
kind = ConstantValueKind.Null,
setType = true
)
}
}
}
private fun createJsPlainObjectCopyFunction(
callableId: CallableId,
parent: FirClassSymbol<*>,
jsPlainObjectInterface: FirRegularClassSymbol,
): FirSimpleFunction {
val interfaceType = jsPlainObjectInterface.defaultType()
return createJsPlainObjectsFunction(callableId, parent, jsPlainObjectInterface) {
buildPropertyAccessExpression {
dispatchReceiver = buildThisReceiverExpression {
calleeReference = buildImplicitThisReference { boundSymbol = jsPlainObjectInterface }
coneTypeOrNull = interfaceType
}
calleeReference = buildResolvedNamedReference {
name = this@createJsPlainObjectsFunction.name
resolvedSymbol = this@createJsPlainObjectsFunction
}
coneTypeOrNull = resolvedReturnType
}
}
}
@OptIn(SymbolInternals::class)
private fun createJsPlainObjectsFunction(
callableId: CallableId,
parent: FirClassSymbol<*>,
jsPlainObjectInterface: FirRegularClassSymbol,
getParameterDefaultValueFromProperty: FirPropertySymbol.() -> FirExpression?
): FirSimpleFunction {
val jsPlainObjectProperties = session.jsPlainObjectPropertiesProvider.getJsPlainObjectsPropertiesForClass(jsPlainObjectInterface)
val functionTarget = FirFunctionTarget(null, isLambda = false)
val jsPlainObjectInterfaceDefaultType = jsPlainObjectInterface.defaultType()
return buildSimpleFunction {
moduleData = jsPlainObjectInterface.moduleData
resolvePhase = FirResolvePhase.BODY_RESOLVE
origin = JsPlainObjectsPluginKey.origin
symbol = FirNamedFunctionSymbol(callableId)
name = callableId.callableName
returnTypeRef = jsPlainObjectInterfaceDefaultType.toFirResolvedTypeRef()
status = FirResolvedDeclarationStatusImpl(
jsPlainObjectInterface.visibility,
Modality.FINAL,
jsPlainObjectInterface.visibility.toEffectiveVisibility(parent, forClass = true)
).apply {
isInline = true
isOperator = true
}
dispatchReceiverType = parent.defaultType()
jsPlainObjectInterface.typeParameterSymbols.mapTo(typeParameters) { it.fir }
jsPlainObjectProperties.mapTo(valueParameters) {
val typeRef = it.resolvedReturnTypeRef
buildValueParameter {
moduleData = session.moduleData
origin = JsPlainObjectsPluginKey.origin
returnTypeRef = typeRef
name = it.name
symbol = FirValueParameterSymbol(it.name)
isCrossinline = false
isNoinline = true
isVararg = false
resolvePhase = FirResolvePhase.BODY_RESOLVE
containingFunctionSymbol = this@buildSimpleFunction.symbol
defaultValue = it.getParameterDefaultValueFromProperty()
}
}
}.also(functionTarget::bind)
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir
import org.jetbrains.kotlin.fir.extensions.predicate.DeclarationPredicate
import org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate
import org.jetbrains.kotlinx.jspo.compiler.resolve.JsPlainObjectsAnnotations
object JsPlainObjectsPredicates {
internal object AnnotatedWithJsPlainObject {
private val jsPlainObjectAnnotation = setOf(JsPlainObjectsAnnotations.jsPlainObjectAnnotationFqName)
internal val LOOKUP = LookupPredicate.create { annotated(jsPlainObjectAnnotation) }
internal val DECLARATION = DeclarationPredicate.create { annotated(jsPlainObjectAnnotation) }
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir.checkers
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
class FirJsPlainObjectsCheckersComponent(session: FirSession) : FirAdditionalCheckersExtension(session) {
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker> = setOf(FirJsPlainObjectsPluginClassChecker)
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
object FirJsPlainObjectsErrors {
val NON_EXTERNAL_DECLARATIONS_NOT_SUPPORTED by error1<PsiElement, String>()
val ONLY_INTERFACES_ARE_SUPPORTED by error1<PsiElement, String>()
val IMPLEMENTING_OF_JS_PLAIN_OBJECT_IS_NOT_SUPPORTED by error1<PsiElement, String>()
val METHODS_ARE_NOT_ALLOWED_INSIDE_JS_PLAIN_OBJECT by error0<PsiElement>()
val JS_PLAIN_OBJECT_CAN_EXTEND_ONLY_OTHER_JS_PLAIN_OBJECTS by error1<PsiElement, String>()
init {
RootDiagnosticRendererFactory.registerFactory(KtDefaultErrorMessagesJsPlainObjects)
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir.checkers
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isEffectivelyExternal
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.declarations.utils.isMethodOfAny
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.scopes.processAllFunctions
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isAny
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlinx.jspo.compiler.resolve.JsPlainObjectsAnnotations
object FirJsPlainObjectsPluginClassChecker : FirClassChecker() {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
with(context) {
val classSymbol = declaration.symbol as? FirRegularClassSymbol ?: return
if (classSymbol.hasAnnotation(JsPlainObjectsAnnotations.jsPlainObjectAnnotationClassId, session)) {
checkJsPlainObjectAnnotationTargets(classSymbol, reporter)
checkJsPlainObjectSuperTypes(classSymbol, reporter)
checkJsPlainObjectMembers(classSymbol, reporter)
} else {
checkJsPlainObjectAsSuperInterface(classSymbol, reporter)
}
}
}
context(CheckerContext)
private fun checkJsPlainObjectAnnotationTargets(classSymbol: FirClassSymbol<out FirClass>, reporter: DiagnosticReporter) {
val classKind = classSymbol.classKind.codeRepresentation ?: error("Unexpected enum entry")
if (!classSymbol.isEffectivelyExternal(session)) {
reporter.reportOn(classSymbol.source, FirJsPlainObjectsErrors.NON_EXTERNAL_DECLARATIONS_NOT_SUPPORTED, classKind)
return
}
if (!classSymbol.isInterface) {
reporter.reportOn(classSymbol.source, FirJsPlainObjectsErrors.ONLY_INTERFACES_ARE_SUPPORTED, classKind)
return
}
}
context(CheckerContext)
private fun checkJsPlainObjectMembers(classSymbol: FirClassSymbol<out FirClass>, reporter: DiagnosticReporter) {
if (!classSymbol.isEffectivelyExternal(session) || !classSymbol.isInterface) return
classSymbol
.declaredMemberScope(session, null)
.processAllFunctions {
if (!it.isMethodOfAny && !it.isInline) {
reporter.reportOn(it.source, FirJsPlainObjectsErrors.METHODS_ARE_NOT_ALLOWED_INSIDE_JS_PLAIN_OBJECT)
}
}
}
context(CheckerContext)
private fun checkJsPlainObjectSuperTypes(classSymbol: FirClassSymbol<out FirClass>, reporter: DiagnosticReporter) {
if (!classSymbol.isEffectivelyExternal(session) || !classSymbol.isInterface) return
classSymbol.resolvedSuperTypeRefs.forEach { superType ->
val superInterface = superType.coneType
.takeIf { !it.isAny }
?.fullyExpandedType(session)
?.toRegularClassSymbol(session) ?: return@forEach
if (!superInterface.hasAnnotation(JsPlainObjectsAnnotations.jsPlainObjectAnnotationClassId, session)) {
reporter.reportOn(
superType.source,
FirJsPlainObjectsErrors.JS_PLAIN_OBJECT_CAN_EXTEND_ONLY_OTHER_JS_PLAIN_OBJECTS,
classSymbol.classId.asFqNameString()
)
}
}
}
context(CheckerContext)
private fun checkJsPlainObjectAsSuperInterface(classSymbol: FirClassSymbol<out FirClass>, reporter: DiagnosticReporter) {
classSymbol.resolvedSuperTypeRefs.forEach {
val superInterface = it.coneType.fullyExpandedType(session)
.toRegularClassSymbol(session)
?.takeIf { it.classKind == ClassKind.INTERFACE } ?: return@forEach
if (superInterface.hasAnnotation(JsPlainObjectsAnnotations.jsPlainObjectAnnotationClassId, session)) {
reporter.reportOn(
it.source,
FirJsPlainObjectsErrors.IMPLEMENTING_OF_JS_PLAIN_OBJECT_IS_NOT_SUPPORTED,
classSymbol.classId.asFqNameString()
)
}
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir.checkers
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers
object KtDefaultErrorMessagesJsPlainObjects : BaseDiagnosticRendererFactory() {
override val MAP = KtDiagnosticFactoryToRendererMap("JsPlainObjects").apply {
put(
FirJsPlainObjectsErrors.NON_EXTERNAL_DECLARATIONS_NOT_SUPPORTED,
"Non-external {0} can not be annotated with JsPlainObjects. Only external interfaces are supported.",
CommonRenderers.STRING
)
put(
FirJsPlainObjectsErrors.ONLY_INTERFACES_ARE_SUPPORTED,
"External {0} can not be annotated with JsPlainObjects. Only external interfaces are supported.",
CommonRenderers.STRING
)
put(
FirJsPlainObjectsErrors.IMPLEMENTING_OF_JS_PLAIN_OBJECT_IS_NOT_SUPPORTED,
"[{0}] is marked as JsPlainObjects, so, it can not be used as a super-type for non-JsPlainObjects declarations",
CommonRenderers.STRING
)
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.jspo.compiler.fir.services
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.caches.FirCache
import org.jetbrains.kotlin.fir.caches.createCache
import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.scopes.processAllProperties
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlinx.jspo.compiler.fir.JsPlainObjectsPredicates
import org.jetbrains.kotlinx.jspo.compiler.resolve.JsPlainObjectsAnnotations
class JsPlainObjectsPropertiesProvider(session: FirSession) : FirExtensionSessionComponent(session) {
private val cache: FirCache<FirClassSymbol<*>, List<FirPropertySymbol>, Nothing?> =
session.firCachesFactory.createCache(this::createJsPlainObjectProperties)
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(JsPlainObjectsPredicates.AnnotatedWithJsPlainObject.DECLARATION)
}
fun getJsPlainObjectsPropertiesForClass(classSymbol: FirClassSymbol<*>): List<FirPropertySymbol> {
return cache.getValue(classSymbol)
}
private fun createJsPlainObjectProperties(classSymbol: FirClassSymbol<*>): List<FirPropertySymbol> =
if (!classSymbol.hasAnnotation(JsPlainObjectsAnnotations.jsPlainObjectAnnotationClassId, session)) {
emptyList()
} else {
buildList {
classSymbol.resolvedSuperTypes.forEach {
val superInterface = it.fullyExpandedType(session)
.toRegularClassSymbol(session)
?.takeIf { it.classKind == ClassKind.INTERFACE } ?: return@forEach
val superInterfaceSimpleObjectProperties = createJsPlainObjectProperties(superInterface)
superInterfaceSimpleObjectProperties.forEach(::addIfNotNull)
}
classSymbol
.declaredMemberScope(session, null)
.processAllProperties {
addIfNotNull(it.takeIf { it.visibility == Visibilities.Public } as? FirPropertySymbol)
}
}
}
}
val FirSession.jsPlainObjectPropertiesProvider: JsPlainObjectsPropertiesProvider by FirSession.sessionComponentAccessor()