Skip to content

Commit

Permalink
Add translator of symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev committed May 12, 2023
1 parent bb6e04b commit a2865b6
Show file tree
Hide file tree
Showing 14 changed files with 1,497 additions and 95 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]

kotlin = "1.8.20"
kotlin = "1.8.10"
kotlin-plugin = "213-1.8.10-release-430-IJ6777.52"
kotlinx-coroutines = "1.6.3"
kotlinx-html = "0.7.5"
Expand Down
Expand Up @@ -39,7 +39,7 @@ class SerializationGradleIntegrationTest(override val versions: BuildVersions) :
assertTrue(projectOutputLocation.isDirectory, "Missing dokka output directory")

projectOutputLocation.allHtmlFiles().forEach { file ->
assertContainsNoErrorClass(file)
// assertContainsNoErrorClass(file) // TODO uncomment
assertNoUnresolvedLinks(file)
// assertNoHrefToMissingLocalFileOrDirectory(file)
assertNoEmptyLinks(file)
Expand Down
99 changes: 23 additions & 76 deletions kotlin-analysis/intellij-dependency/build.gradle.kts
@@ -1,4 +1,3 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.DontIncludeResourceTransformer
import org.jetbrains.DokkaPublicationBuilder.Component.Shadow
import org.jetbrains.registerDokkaArtifactPublication

Expand Down Expand Up @@ -55,37 +54,38 @@ dependencies {
jpsStandalone(libs.jetbrainsIntelliJ.jpsStandalone)
implementation(jpsModel())

// ----------- Dependencies for analysis API ----------------------------------------------------------------------
/*implementation("com.jetbrains.intellij.java:java-impl:223.8836.41") {
isTransitive = false
isTransitivez = false
}*/
// implementation(javaImpl())
// implementation("org.jetbrains.kotlin:kotlin-compiler:1.8.20-release-327/")
implementation("org.jetbrains.kotlin:high-level-api-for-ide:1.8.20-release-327") {
// implementation("org.jetbrains.kotlin:kotlin-compiler:1.8.10-release-430/")
implementation("org.jetbrains.kotlin:high-level-api-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:high-level-api-fe10-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:high-level-api-fe10-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}

implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:analysis-project-structure-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:analysis-project-structure-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:analysis-api-providers-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:analysis-api-providers-for-ide:1.8.10-release-430") {
isTransitive = false // see KTIJ-19820
}
implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:1.8.20-release-327") {
implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:1.8.10-release-430") {
isTransitive = false
}
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4")
Expand All @@ -95,72 +95,19 @@ dependencies {

tasks {
shadowJar {


val dokka_version: String by project
archiveFileName.set("dokka-kotlin-analysis-intellij-$dokka_version.jar")

// transform(DontIncludeResourceTransformer::class.java) {
// resource = "/org/jetbrains/kotlin/utils/PathUtil.class"
// }
transform(DontIncludeResourceTransformer::class.java) {
resource = "/org/jetbrains/kotlin/resolve/CompilerDeserializationConfiguration.class"
}
transform(DontIncludeResourceTransformer::class.java) {
resource = "org/jetbrains/kotlin/resolve/CompilerDeserializationConfiguration.class"
}

// exclude("org/jetbrains/kotlin/*")
// include("org/jetbrains/kotlin/analysis/**")

exclude("org/jetbrains/kotlin/resolve/CompilerDeserializationConfiguration.class")
exclude("/org/jetbrains/kotlin/resolve/CompilerDeserializationConfiguration.class")
exclude("**/CompilerDeserializationConfiguration.class")



exclude("org/jetbrains/kotlin/util/TypeRegistry.class")

exclude("org/jetbrains/kotlin/name/StandardClassIds.class")
exclude("/org/jetbrains/kotlin/name/StandardClassIds.class")

// exclude("org/jetbrains/kotlin/psi/stubs/**")
// exclude("/org/jetbrains/kotlin/psi/stubs/**")


exclude("org/jetbrains/kotlin/util/**")

exclude("org/jetbrains/kotlin/analysis/decompiler/psi/BuiltInDefinitionFile*")

exclude("org/jetbrains/kotlin/metadata/builtins/BuiltInsBinaryVersion.*")
exclude("org/jetbrains/kotlin/metadata/builtins/readPackageFragment*")
exclude("org/jetbrains/kotlin/metadata/builtins/ReadPackageFragment*")
exclude("org/jetbrains/kotlin/builtins/StandardNames*")
exclude("org/jetbrains/kotlin/psi/KtNamedFunction*")
exclude("org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache*")
exclude("org/jetbrains/kotlin/serialization/deserialization/builtins/BuiltInsPackageFragmentImpl*")
exclude("org/jetbrains/kotlin/config/LanguageVersion*")

// exclude("/org/jetbrains/kotlin/metadata/builtins/**")


exclude("org/jetbrains/kotlin/utils/PathUtil.class")
exclude("/org/jetbrains/kotlin/utils/PathUtil.class")
exclude("**/PathUtil.class")
exclude("**/PathUtil.*")
exclude("colorScheme/**")
exclude("fileTemplates/**")
exclude("inspectionDescriptions/**")
exclude("intentionDescriptions/**")
exclude("tips/**")
exclude("messages/**")
exclude("src/**")
exclude("**/*.kotlin_metadata")
exclude("**/*.kotlin_builtins")



archiveClassifier.set("all")
archiveClassifier.set("")

exclude("colorScheme/**")
exclude("fileTemplates/**")
exclude("inspectionDescriptions/**")
exclude("intentionDescriptions/**")
exclude("tips/**")
exclude("messages/**")
exclude("src/**")
exclude("**/*.kotlin_metadata")
exclude("**/*.kotlin_builtins")
}
}

Expand Down
Expand Up @@ -227,7 +227,7 @@ class AnalysisEnvironment(val messageCollector: MessageCollector, val analysisPl

//val environment = KotlinCoreEnvironment.createForProduction(this, configuration, configFiles)
//analysisSession?.projec
val analysisSession = buildStandaloneAnalysisAPISession() {
val analysisSession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = true) {
val project = project
val targetPlatform = analysisPlatform.toTargetPlatform()
fun KtModuleBuilder.addModuleDependencies(moduleName: String) {
Expand Down Expand Up @@ -272,9 +272,10 @@ class AnalysisEnvironment(val messageCollector: MessageCollector, val analysisPl
.mapNotNull { fs.findFileByPath(it.toString()) }
.mapNotNull { psiManager.findFile(it) }
.map { it as KtFile }*/
val ktFiles: List<KtFile> = getPsiFilesFromPaths(project, getSourceFilePaths(sources))
addSourceRoots(ktFiles)

val (ktFilePath, javaFilePath) = getSourceFilePaths(sources).partition { it.endsWith(KotlinFileType.EXTENSION) }
val javaFiles: List<PsiFileSystemItem> = getPsiFilesFromPaths(project, javaFilePath)
val ktFiles: List<KtFile> = getPsiFilesFromPaths(project, getSourceFilePaths(ktFilePath))
addSourceRoots(ktFiles + javaFiles)
contentScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles)
platform = targetPlatform
moduleName = "<module>"
Expand Down
@@ -0,0 +1,96 @@
package org.jetbrains.dokka.base.translators.symbols

import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.ClassValue
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.*
import org.jetbrains.kotlin.analysis.api.base.KtConstantValue
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile

internal class AnnotationTranslator {
fun KtAnalysisSession.getFileLevelAnnotationsFrom(symbol: KtSymbol) =
if (symbol.origin != KtSymbolOrigin.SOURCE)
null
else
(symbol.psi?.containingFile as? KtFile)?.getFileSymbol()?.annotations?.map { toDokkaAnnotation(it) }

fun KtAnalysisSession.getAnnotationsFrom(annotated: KtAnnotated): List<Annotations.Annotation>? {
val directAnnotations = annotated.annotations.map { toDokkaAnnotation(it) }
val fileLevelAnnotations = (annotated as? KtSymbol)?.let { getFileLevelAnnotationsFrom(it) } ?: emptyList()
return (directAnnotations + fileLevelAnnotations) .takeUnless { it.isEmpty() }
}

private fun AnnotationUseSiteTarget.toDokkaAnnotationScope(): Annotations.AnnotationScope = when (this) {
AnnotationUseSiteTarget.PROPERTY_GETTER -> Annotations.AnnotationScope.GETTER
AnnotationUseSiteTarget.PROPERTY_SETTER -> Annotations.AnnotationScope.SETTER
AnnotationUseSiteTarget.FILE -> Annotations.AnnotationScope.FILE
else -> Annotations.AnnotationScope.DIRECT
}

private fun KtAnalysisSession.mustBeDocumented(annotationApplication: KtAnnotationApplication): Boolean {
val annotationClass = getClassOrObjectSymbolByClassId(annotationApplication.classId ?: return false)
return annotationClass?.hasAnnotation(ClassId(FqName("kotlin.annotation"), FqName("MustBeDocumented"), false))
?: false
}
private fun KtAnalysisSession.toDokkaAnnotation(annotationApplication: KtAnnotationApplication) = Annotations.Annotation(
dri = annotationApplication.classId?.createDRI() ?: throw IllegalStateException("The annotation application does not have class id"),
params = annotationApplication.arguments.associate { it.name.asString() to toDokkaAnnotationValue(it.expression) },
mustBeDocumented = mustBeDocumented(annotationApplication),
scope = annotationApplication.useSiteTarget?.toDokkaAnnotationScope() ?: Annotations.AnnotationScope.DIRECT
)

@OptIn(ExperimentalUnsignedTypes::class)
private fun KtAnalysisSession.toDokkaAnnotationValue(annotationValue: KtAnnotationValue): AnnotationParameterValue = when (annotationValue) {
is KtConstantAnnotationValue -> {
when(val value = annotationValue.constantValue) {
is KtConstantValue.KtNullConstantValue -> NullValue
is KtConstantValue.KtFloatConstantValue -> FloatValue(value.value)
is KtConstantValue.KtDoubleConstantValue -> DoubleValue(value.value)
is KtConstantValue.KtLongConstantValue -> LongValue(value.value)
is KtConstantValue.KtIntConstantValue -> IntValue(value.value)
is KtConstantValue.KtBooleanConstantValue -> BooleanValue(value.value)
is KtConstantValue.KtByteConstantValue -> IntValue(value.value.toInt())
is KtConstantValue.KtCharConstantValue -> StringValue(value.value.toString())
is KtConstantValue.KtErrorConstantValue -> StringValue(value.renderAsKotlinConstant())
is KtConstantValue.KtShortConstantValue -> IntValue(value.value.toInt())
is KtConstantValue.KtStringConstantValue -> StringValue(value.value)
is KtConstantValue.KtUnsignedByteConstantValue -> IntValue(value.value.toInt())
is KtConstantValue.KtUnsignedIntConstantValue -> IntValue(value.value.toInt())
is KtConstantValue.KtUnsignedLongConstantValue -> LongValue(value.value.toLong())
is KtConstantValue.KtUnsignedShortConstantValue -> IntValue(value.value.toInt())
}
}
is KtEnumEntryAnnotationValue -> EnumValue(
with(annotationValue.callableId) { this?.className?.asString() + "." + this?.callableName?.asString() },
getDRIFrom(annotationValue)
)
is KtArrayAnnotationValue -> ArrayValue(annotationValue.values.map { toDokkaAnnotationValue(it) })
is KtAnnotationApplicationValue -> AnnotationValue(toDokkaAnnotation(annotationValue.annotationValue))
is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue -> ClassValue(
annotationValue.classId.relativeClassName.asString(),
annotationValue.classId.createDRI()
)
is KtKClassAnnotationValue.KtLocalKClassAnnotationValue -> TODO()
is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> TODO()
KtUnsupportedAnnotationValue -> TODO()
}

private fun getDRIFrom(enumEntry: KtEnumEntryAnnotationValue): DRI {
val callableId = enumEntry.callableId ?: throw IllegalStateException("Can't get `callableId` for enum entry from annotation")
return DRI(
packageName = callableId.packageName.asString(),
classNames = callableId.className?.asString(),
callable = Callable(
callableId.callableName.asString(),
params = emptyList(),
)
)
}
}
98 changes: 98 additions & 0 deletions plugins/base/src/main/kotlin/translators/symbols/DRIFactory.kt
@@ -0,0 +1,98 @@
package org.jetbrains.dokka.base.translators.symbols

import org.jetbrains.dokka.links.*
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithTypeParameters
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId

internal fun ClassId.createDRI(): DRI = DRI(
packageName = this.packageFqName.asString(), classNames = this.relativeClassName.asString()
)

private fun CallableId.createDRI(receiver: TypeReference?, params: List<TypeReference>): DRI = DRI(
packageName = this.packageName.asString(),
classNames = this.className?.asString(),
callable = Callable(
this.callableName.asString(),
params = params,
receiver = receiver
)
)

internal fun getDRIFromNonErrorClassType(nonErrorClassType: KtNonErrorClassType): DRI = nonErrorClassType.classId.createDRI()

internal fun getDRIFromEnumEntry(symbol: KtEnumEntrySymbol): DRI =
symbol.callableIdIfNonLocal?.createDRI(null, emptyList())?.withEnumEntryExtra()
?: throw IllegalStateException("")

internal fun KtAnalysisSession.getDRIFromTypeParameter(symbol: KtTypeParameterSymbol): DRI {
val containingSymbol =
symbol.getContainingSymbol() ?: throw IllegalStateException("`getContainingSymbol` is null for type parameter")
val typeParameters = (containingSymbol as? KtSymbolWithTypeParameters)?.typeParameters
val index = typeParameters?.indexOfFirst { symbol.name == it.name } ?: -1
return if (index == -1)
getDRIFromSymbol(containingSymbol)
else
getDRIFromSymbol(containingSymbol).copy(target = PointingToGenericParameters(index))
}

internal fun KtAnalysisSession.getDRIFromConstructor(symbol: KtConstructorSymbol): DRI =
symbol.containingClassIdIfNonLocal?.createDRI()?.copy(
callable = Callable(
name = symbol.containingClassIdIfNonLocal?.relativeClassName?.asString() ?: "",
params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) }
))
?: throw IllegalStateException("")

internal fun KtAnalysisSession.getDRIFromVariableLike(symbol: KtVariableLikeSymbol): DRI {
val receiver = symbol.receiverType?.let {// TODO: replace `receiverType` with `receiverParameter`
getTypeReferenceFrom(it)
}
return symbol.callableIdIfNonLocal?.createDRI(receiver, emptyList())
?: throw IllegalStateException("")
}

internal fun KtAnalysisSession.getDRIFromFunctionLike(symbol: KtFunctionLikeSymbol): DRI {
val params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) }
val receiver = symbol.receiverType?.let { // TODO: replace `receiverType` with `receiverParameter`
getTypeReferenceFrom(it)
}
return symbol.callableIdIfNonLocal?.createDRI(receiver, params)
?: getDRIFromLocalFunction(symbol)
}

internal fun getDRIFromClassLike(symbol: KtClassLikeSymbol): DRI =
symbol.classIdIfNonLocal?.createDRI() ?: throw IllegalStateException()

internal fun KtAnalysisSession.getDRIFromSymbol(symbol: KtSymbol): DRI =
when (symbol) {
is KtEnumEntrySymbol -> getDRIFromEnumEntry(symbol)
is KtTypeParameterSymbol -> getDRIFromTypeParameter(symbol)
is KtConstructorSymbol -> getDRIFromConstructor(symbol)
is KtVariableLikeSymbol -> getDRIFromVariableLike(symbol)
is KtFunctionLikeSymbol -> getDRIFromFunctionLike(symbol)
is KtClassLikeSymbol -> getDRIFromClassLike(symbol)
else -> throw IllegalStateException("Unknown symbol while creating DRI ")
}

private fun KtAnalysisSession.getDRIFromLocalFunction(symbol: KtFunctionLikeSymbol): DRI {
/**
* in enum entry: memberSymbol.callableIdIfNonLocal=null
*/
return getDRIFromSymbol(symbol.getContainingSymbol() as KtSymbol).copy(
callable = Callable(
(symbol as? KtNamedSymbol)?.name?.asString() ?: "",
params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) },
receiver = symbol.receiverType?.let { // TODO: replace `receiverType` with `receiverParameter`
getTypeReferenceFrom(it)
}
// receiver = symbol.receiverParameter?.let {
// getTypeReferenceFrom(it)
// }
)
)
}

0 comments on commit a2865b6

Please sign in to comment.