| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * 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 com.google.devsite.renderer.converters |
| |
| import com.google.devsite.ClasslikeSummaryList |
| import com.google.devsite.ConstructorSummaryList |
| import com.google.devsite.FunctionSummaryList |
| import com.google.devsite.LinkDescriptionSummaryList |
| import com.google.devsite.PropertySummaryList |
| import com.google.devsite.TypeSummaryItem |
| import com.google.devsite.components.Link |
| import com.google.devsite.components.impl.DefaultClassHierarchy |
| import com.google.devsite.components.impl.DefaultClasslike |
| import com.google.devsite.components.impl.DefaultClasslikeDescription |
| import com.google.devsite.components.impl.DefaultClasslikeSignature |
| import com.google.devsite.components.impl.DefaultClasslikeSummary |
| import com.google.devsite.components.impl.DefaultDevsitePage |
| import com.google.devsite.components.impl.DefaultDevsitePlatformSelector |
| import com.google.devsite.components.impl.DefaultInheritedSymbols |
| import com.google.devsite.components.impl.DefaultRelatedSymbols |
| import com.google.devsite.components.impl.DefaultSummaryList |
| import com.google.devsite.components.impl.DefaultTableRowSummaryItem |
| import com.google.devsite.components.impl.DefaultTableTitle |
| import com.google.devsite.components.impl.emptyInheritedSymbolsList |
| import com.google.devsite.components.impl.emptySummaryList |
| import com.google.devsite.components.pages.Classlike |
| import com.google.devsite.components.pages.Classlike.TitledList |
| import com.google.devsite.components.pages.DevsitePage |
| import com.google.devsite.components.symbols.ClasslikeDescription |
| import com.google.devsite.components.symbols.ClasslikeSignature |
| import com.google.devsite.components.symbols.ClasslikeSummary |
| import com.google.devsite.components.symbols.FunctionSignature |
| import com.google.devsite.components.symbols.PropertySignature |
| import com.google.devsite.components.symbols.SymbolDetail |
| import com.google.devsite.components.symbols.SymbolSignature |
| import com.google.devsite.components.symbols.SymbolSummary |
| import com.google.devsite.components.symbols.TypeProjectionComponent |
| import com.google.devsite.components.table.ClassHierarchy |
| import com.google.devsite.components.table.InheritedSymbolsList |
| import com.google.devsite.components.table.RelatedSymbols |
| import com.google.devsite.components.table.SummaryItem |
| import com.google.devsite.components.table.SummaryList |
| import com.google.devsite.components.table.TableRowSummaryItem |
| import com.google.devsite.components.table.TableTitle |
| import com.google.devsite.renderer.Language |
| import com.google.devsite.renderer.impl.ClassGraph |
| import com.google.devsite.renderer.impl.DocumentablesHolder |
| import com.google.devsite.renderer.impl.paths.FilePathProvider |
| import com.google.devsite.strictSingleOrNull |
| import com.jetbrains.rd.util.concurrentMapOf |
| import kotlinx.coroutines.async |
| import kotlinx.coroutines.coroutineScope |
| import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet |
| import org.jetbrains.dokka.links.Callable |
| import org.jetbrains.dokka.links.DRI |
| import org.jetbrains.dokka.links.parent |
| import org.jetbrains.dokka.model.ActualTypealias |
| import org.jetbrains.dokka.model.Annotations |
| import org.jetbrains.dokka.model.Bound |
| import org.jetbrains.dokka.model.DAnnotation |
| import org.jetbrains.dokka.model.DClasslike |
| import org.jetbrains.dokka.model.DEnum |
| import org.jetbrains.dokka.model.DEnumEntry |
| import org.jetbrains.dokka.model.DFunction |
| import org.jetbrains.dokka.model.DObject |
| import org.jetbrains.dokka.model.DProperty |
| import org.jetbrains.dokka.model.DTypeParameter |
| import org.jetbrains.dokka.model.Documentable |
| import org.jetbrains.dokka.model.ExtraModifiers |
| import org.jetbrains.dokka.model.GenericTypeConstructor |
| import org.jetbrains.dokka.model.InheritedMember |
| import org.jetbrains.dokka.model.KotlinModifier |
| import org.jetbrains.dokka.model.WithCompanion |
| import org.jetbrains.dokka.model.WithConstructors |
| import org.jetbrains.dokka.model.WithSources |
| import org.jetbrains.dokka.model.WithSupertypes |
| import org.jetbrains.dokka.model.properties.PropertyContainer |
| import org.jetbrains.dokka.model.properties.WithExtraProperties |
| import org.jetbrains.dokka.model.toAdditionalModifiers |
| |
| /** Converts documentable class-likes into the classlike component. */ |
| internal abstract class ClasslikeDocumentableConverter( |
| private val displayLanguage: Language, |
| protected val classlike: DClasslike, |
| private val pathProvider: FilePathProvider, |
| protected val docsHolder: DocumentablesHolder, |
| protected val functionConverter: FunctionDocumentableConverter, |
| protected val propertyConverter: PropertyDocumentableConverter, |
| protected val enumConverter: EnumValueDocumentableConverter, // TODO(KMP b/256172699) |
| protected val javadocConverter: DocTagConverter, // TODO(KMP b/254490320) |
| protected val paramConverter: ParameterDocumentableConverter, |
| private val annotationConverter: AnnotationDocumentableConverter, |
| private val metadataConverter: MetadataConverter, |
| private val classExtensionFunctions: List<DFunction> = emptyList(), |
| private val classExtensionProperties: List<DProperty> = emptyList() |
| ) { |
| protected abstract val header: DefaultDevsitePlatformSelector? |
| protected abstract val functionToSummaryConverter: |
| (DFunction, ModifierHints) -> TypeSummaryItem<FunctionSignature>? |
| protected abstract val functionToDetailConverter: |
| (DFunction, ModifierHints) -> SymbolDetail<FunctionSignature>? |
| protected abstract val propertyToSummaryConverter: |
| (DProperty, ModifierHints) -> TypeSummaryItem<PropertySignature>? |
| protected abstract val propertyToDetailConverter: |
| (DProperty, ModifierHints) -> SymbolDetail<PropertySignature>? |
| protected abstract val constructorToSummaryConverter: |
| (DFunction) -> TableRowSummaryItem<Nothing?, SymbolSummary<FunctionSignature>>? |
| protected abstract val constructorToDetailConverter: |
| (DFunction, ModifierHints) -> SymbolDetail<FunctionSignature>? |
| |
| /** @return the classlike component */ |
| suspend fun classlike(): DevsitePage<Classlike> = coroutineScope { |
| var (declaredFunctions, declaredProperties) = classlike.nonInheritedTypes() |
| val inheritedAll = (classlike.children + classlike.properties.gettersAndSetters()) |
| .inheritedTypes(classlike.supertypesForDisplayLanguage()) |
| var (companionFunctions, companionProperties) = classlike.companionFunctionsAndProperties() |
| |
| // Java documentation needs to respect @jvm* annotations |
| if (displayLanguage == Language.JAVA) { |
| declaredFunctions = declaredFunctions.filterOutJvmSynthetic().map { it.withJvmName() } |
| declaredProperties = declaredProperties.filterOutJvmSynthetic() |
| } |
| |
| // Some symbols are moved from the companion object type to the enclosing class in java |
| if (displayLanguage == Language.JAVA) { |
| // Objects that are not top-level |
| if (classlike is DObject && docsHolder.isCompanion(classlike)) { |
| // Hoist companion JvmFields |
| declaredProperties = declaredProperties.filterNot { it.isJvmField() } |
| } else if (classlike is DObject) { |
| declaredProperties += DProperty( |
| dri = classlike.dri.copy( |
| callable = Callable(name = "INSTANCE", params = emptyList()) |
| ), |
| name = "INSTANCE", |
| documentation = emptyMap(), |
| expectPresentInSet = classlike.expectPresentInSet, |
| sources = classlike.sources, |
| visibility = classlike.visibility, |
| type = GenericTypeConstructor(dri = classlike.dri, projections = emptyList()), |
| receiver = null, |
| setter = null, |
| getter = null, |
| modifier = emptyMap(), |
| sourceSets = classlike.sourceSets, |
| generics = emptyList(), |
| isExpectActual = false, |
| extra = PropertyContainer.withAll( |
| classlike.sourceSets.map { |
| mapOf( |
| it to setOf(ExtraModifiers.JavaOnlyModifiers.Static) |
| ).toAdditionalModifiers() |
| } + classlike.sourceSets.map { |
| Annotations( |
| mapOf( |
| it to listOf( |
| Annotations.Annotation( |
| dri = DRI( |
| packageName = "kotlin.jvm", |
| classNames = "JvmField" |
| ), |
| params = emptyMap() |
| ) |
| ) |
| ) |
| ) |
| } |
| ) |
| ) |
| } else { |
| // Classlikes that are not (top-level) objects |
| declaredProperties += companionProperties.filter { it.isJavaStaticField() } |
| declaredProperties += companionProperties.filter { it.isJvmFieldAnnotated() }.map { |
| // It is technically incorrect to put @JvmStatic on a property, but we use this |
| // to remember that we should later inject the `static` modifier to this |
| it.withNewExtras(it.extra.addAnnotation(JvmStatic)) |
| } |
| declaredFunctions += companionFunctions.filter { it.isJavaStaticMethod() } |
| } |
| } |
| |
| declaredFunctions = declaredFunctions.sortedWith(functionSignatureComparator()) |
| declaredProperties = declaredProperties.sortedBy { it.name } |
| companionFunctions = companionFunctions.sortedWith(functionSignatureComparator()) |
| companionProperties = companionProperties.sortedBy { it.name } |
| |
| val enumValues = (classlike as? DEnum)?.entries.orEmpty().sortedBy { it.name } |
| |
| var allConstructors = (classlike as? WithConstructors)?.constructors.orEmpty() |
| .sortedWith(functionSignatureComparator()) |
| if (classlike is WithConstructors && allConstructors.isEmpty() && classlike.isFromJava() && |
| classlike !is DAnnotation |
| ) { |
| allConstructors = listOf(createDefaultConstructorFor(classlike)) |
| } |
| val enumValuesSummary = async { |
| enumValuesToSummary(enumValuesTitle(), enumValues) |
| } |
| val nestedTypesSummary = async { |
| nestedTypesToSummary( |
| // These are filtered for not-shown classlikes when accessed |
| docsHolder.nestedClasslikesFor(classlike, displayLanguage), |
| docsHolder.classGraph() |
| ) |
| } |
| val constantsSummary = async { |
| propertiesToSummary( |
| constantsTitle(), (declaredProperties + companionProperties).constants() |
| ) |
| } |
| val publicPropertiesSummary = async { |
| propertiesToSummary( |
| publicPropertiesTitle(displayLanguage), |
| declaredProperties.filter(::isPublicNonConst) |
| ) |
| } |
| val protectedPropertiesSummary = async { |
| propertiesToSummary( |
| protectedPropertiesTitle(displayLanguage), |
| declaredProperties.filter(::isProtectedNonConst) |
| ) |
| } |
| val publicConstructorsSummary = async { |
| constructorsToSummary( |
| publicConstructorsTitle(), |
| allConstructors.filter(::isPublic) |
| ) |
| } |
| val protectedConstructorsSummary = async { |
| constructorsToSummary( |
| protectedConstructorsTitle(), |
| allConstructors.filter(::isProtected) |
| ) |
| } |
| val publicFunctionsSummary = async { |
| functionsToSummary( |
| publicMethodsTitle(displayLanguage), |
| declaredFunctions.filter(::isPublic) |
| ) |
| } |
| val protectedFunctionsSummary = async { |
| functionsToSummary( |
| protectedMethodsTitle(displayLanguage), |
| declaredFunctions.filter(::isProtected) |
| ) |
| } |
| val publicCompanionFunctionsSummary = async { |
| emptyIfJava() ?: functionsToSummary( |
| publicCompanionFunctionsTitle(), |
| companionFunctions.filter(::isPublic) |
| ) |
| } |
| val protectedCompanionFunctionsSummary = async { |
| emptyIfJava() ?: functionsToSummary( |
| protectedCompanionFunctionsTitle(), |
| companionFunctions.filter(::isProtected) |
| ) |
| } |
| val publicCompanionPropertiesSummary = async { |
| emptyIfJava() ?: propertiesToSummary( |
| publicCompanionPropertiesTitle(), |
| companionProperties.filter(::isPublicNonConst) |
| ) |
| } |
| val protectedCompanionPropertiesSummary = async { |
| emptyIfJava() ?: propertiesToSummary( |
| protectedCompanionPropertiesTitle(), |
| companionProperties.filter(::isProtectedNonConst) |
| ) |
| } |
| |
| val enumDetails = (classlike as? DEnum)?.let { |
| async { enumValuesToDetail(it, enumValues) } |
| } |
| val constantsDetails = |
| async { propertiesToDetail((declaredProperties + companionProperties).constants()) } |
| val publicPropertiesDetails = |
| async { propertiesToDetail(declaredProperties.filter(::isPublicNonConst)) } |
| val protectedPropertiesDetails = |
| async { propertiesToDetail(declaredProperties.filter(::isProtectedNonConst)) } |
| val publicConstructorsDetails = |
| async { constructorsToDetail(allConstructors.filter(::isPublic)) } |
| val protectedConstructorsDetails = |
| async { constructorsToDetail(allConstructors.filter(::isProtected)) } |
| val publicFunctionsDetails = |
| async { functionsToDetail(declaredFunctions.filter(::isPublic)) } |
| val protectedFunctionsDetails = |
| async { functionsToDetail(declaredFunctions.filter(::isProtected)) } |
| val publicCompanionFunctionsDetail = async { |
| emptyListIfJava() ?: functionsToDetail(companionFunctions.filter(::isPublic)) |
| } |
| val protectedCompanionFunctionsDetail = async { |
| emptyListIfJava() ?: functionsToDetail(companionFunctions.filter(::isProtected)) |
| } |
| val publicCompanionPropertiesDetail = async { |
| emptyListIfJava() ?: propertiesToDetail(companionProperties.filter(::isPublicNonConst)) |
| } |
| val protectedCompanionPropertiesDetail = async { |
| emptyListIfJava() |
| ?: propertiesToDetail(companionProperties.filter(::isProtectedNonConst)) |
| } |
| |
| val inheritedTypes = async { computeInheritedSymbols(inheritedAll) } |
| val metadataComponent = async { metadataConverter.getMetadataForClasslike(classlike) } |
| |
| var extensionFunctions = classExtensionFunctions + |
| if (displayLanguage == Language.JAVA) |
| classExtensionProperties.gettersAndSetters(allowDefault = true) |
| else emptyList() |
| extensionFunctions = extensionFunctions |
| // Sort by the class the extension function came from first, so they will be grouped |
| // together in a logical way |
| .sortedBy { nameForSyntheticClass(it) + it.name } |
| // Convert DRIs to this class so link from summary to detail will stay on class page |
| .map { it.withDRIOfClass(classlike) } |
| if (displayLanguage == Language.JAVA) { |
| extensionFunctions = extensionFunctions.filterNot { |
| it.isSuspendFunction() |
| } |
| } |
| val extensionFunctionsSummary = |
| async { functionsToSummary(extensionFunctionsTitle(), extensionFunctions) } |
| val extensionFunctionsDetail = async { functionsToDetail(extensionFunctions) } |
| |
| // Extension properties are only as-Java as accessors, so they count as extension functions |
| var extensionProperties = when (displayLanguage) { |
| Language.KOTLIN -> classExtensionProperties |
| Language.JAVA -> emptyList() |
| } |
| extensionProperties = extensionProperties |
| // Sort by the class the extension property came from first, so they will be grouped |
| // together in a logical way |
| .sortedBy { nameForSyntheticClass(it) + it.name } |
| // Convert DRIs to this class so link from summary to detail will stay on class page |
| .map { it.withDRIOfClass(classlike) } |
| val extensionPropertiesSummary = |
| async { propertiesToSummary(extensionPropertiesTitle(), extensionProperties) } |
| val extensionPropertiesDetail = async { propertiesToDetail(extensionProperties) } |
| |
| val processed = docsHolder.classlikesDone.getAndIncrement() |
| if (processed % 100 == 0) { |
| docsHolder.logger.debug("Dackka: $displayLanguage classlikes processed: $processed") |
| } |
| |
| val (inheritedFunctions, inheritedConstants, inheritedProperties) = inheritedTypes.await() |
| |
| DefaultDevsitePage( |
| DevsitePage.Params( |
| displayLanguage, |
| path = pathProvider.forReference(classlike.dri).url, |
| bookPath = pathProvider.book, |
| title = classlike.name(), |
| content = DefaultClasslike( |
| Classlike.Params( |
| description = getClasslikeDescription(), |
| displayLanguage = displayLanguage, |
| nestedTypesSummary = nestedTypesSummary.await(), |
| enumValuesSummary = enumValuesSummary.await(), |
| enumValuesDetails = TitledList( |
| enumValuesTitle(), |
| enumDetails?.await() ?: emptyList() |
| ), |
| constantsSummary = constantsSummary.await(), |
| constantsDetails = TitledList( |
| constantsTitle(), |
| constantsDetails.await() |
| ), |
| publicCompanionFunctionsSummary = publicCompanionFunctionsSummary.await(), |
| publicCompanionFunctionsDetails = TitledList( |
| publicCompanionFunctionsTitle(), |
| publicCompanionFunctionsDetail.await() |
| ), |
| protectedCompanionFunctionsSummary = protectedCompanionFunctionsSummary |
| .await(), |
| protectedCompanionFunctionsDetails = TitledList( |
| protectedCompanionFunctionsTitle(), |
| protectedCompanionFunctionsDetail.await() |
| ), |
| publicCompanionPropertiesSummary = publicCompanionPropertiesSummary.await(), |
| publicCompanionPropertiesDetails = TitledList( |
| publicCompanionPropertiesTitle(), |
| publicCompanionPropertiesDetail.await() |
| ), |
| protectedCompanionPropertiesSummary = protectedCompanionPropertiesSummary |
| .await(), |
| protectedCompanionPropertiesDetails = TitledList( |
| protectedCompanionPropertiesTitle(), |
| protectedCompanionPropertiesDetail.await() |
| ), |
| publicConstructorsSummary = publicConstructorsSummary.await(), |
| publicConstructorsDetails = TitledList( |
| publicConstructorsTitle(), |
| publicConstructorsDetails.await() |
| ), |
| protectedConstructorsSummary = protectedConstructorsSummary.await(), |
| protectedConstructorsDetails = TitledList( |
| protectedConstructorsTitle(), |
| protectedConstructorsDetails.await() |
| ), |
| publicFunctionsSummary = publicFunctionsSummary.await(), |
| publicFunctionsDetails = TitledList( |
| publicMethodsTitle(displayLanguage), |
| publicFunctionsDetails.await() |
| ), |
| protectedFunctionsSummary = protectedFunctionsSummary.await(), |
| protectedFunctionsDetails = TitledList( |
| protectedMethodsTitle(displayLanguage), |
| protectedFunctionsDetails.await() |
| ), |
| publicPropertiesSummary = publicPropertiesSummary.await(), |
| publicPropertiesDetails = TitledList( |
| publicPropertiesTitle(displayLanguage), |
| publicPropertiesDetails.await() |
| ), |
| protectedPropertiesSummary = protectedPropertiesSummary.await(), |
| protectedPropertiesDetails = TitledList( |
| protectedPropertiesTitle(displayLanguage), |
| protectedPropertiesDetails.await() |
| ), |
| extensionFunctionsSummary = extensionFunctionsSummary.await(), |
| extensionFunctionsDetails = TitledList( |
| extensionFunctionsTitle(), |
| extensionFunctionsDetail.await() |
| ), |
| extensionPropertiesSummary = extensionPropertiesSummary.await(), |
| extensionPropertiesDetails = TitledList( |
| extensionPropertiesTitle(), |
| extensionPropertiesDetail.await() |
| ), |
| inheritedFunctions = inheritedFunctions ?: emptyInheritedSymbolsList(), |
| inheritedConstants = inheritedConstants ?: emptyInheritedSymbolsList(), |
| inheritedProperties = inheritedProperties ?: emptyInheritedSymbolsList(), |
| ) |
| ), |
| metadataComponent = metadataComponent.await(), |
| includedHeadTagPath = pathProvider.includedHeadTagsPath, |
| ) |
| ) |
| } |
| |
| /** This is intended to be used as (some thing) = emptyIfJava ?: actuallyComputeItIfKotlin() */ |
| private fun <T : SummaryItem> emptyIfJava() = |
| if (displayLanguage == Language.JAVA) emptySummaryList<T>() else null |
| |
| private fun <T> emptyListIfJava() = |
| if (displayLanguage == Language.JAVA) emptyList<T>() else null |
| |
| protected open suspend fun getClasslikeDescription(): ClasslikeDescription = coroutineScope { |
| val signature = async { computeSignature(classlike, docsHolder.classGraph()) } |
| val hierarchy = async { computeHierarchy() } |
| val relatedSymbols = async { findRelatedSymbols() } |
| DefaultClasslikeDescription( |
| ClasslikeDescription.Params( |
| header = header, |
| primarySignature = signature.await(), |
| hierarchy = hierarchy.await(), |
| relatedSymbols = relatedSymbols.await(), |
| descriptionDocs = javadocConverter.metadata(classlike) |
| ) |
| ) |
| } |
| |
| private fun nestedTypesToSummary(nestedClasslikes: List<DClasslike>, classGraph: ClassGraph): |
| ClasslikeSummaryList { |
| val components = nestedClasslikes.map { nestedClasslike -> |
| errorContextInjector(nestedClasslike) { |
| DefaultTableRowSummaryItem( |
| TableRowSummaryItem.Params( |
| title = null, |
| description = DefaultClasslikeSummary( |
| ClasslikeSummary.Params( |
| signature = computeSignature(nestedClasslike, classGraph), |
| description = javadocConverter.summaryDescription(nestedClasslike) |
| ) |
| ) as ClasslikeSummary |
| ) |
| ) |
| } |
| } |
| |
| return DefaultSummaryList( |
| SummaryList.Params( |
| header = DefaultTableTitle( |
| TableTitle.Params( |
| title = nestedTypesTitle(), |
| big = true |
| ) |
| ), |
| items = components |
| ) |
| ) |
| } |
| |
| private fun functionsToSummary(name: String? = null, functions: List<DFunction>): |
| FunctionSummaryList { |
| val components = functions.mapNotNull { |
| val modifierHints = ModifierHints( |
| displayLanguage = displayLanguage, |
| type = DFunction::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| isSummary = true, |
| injectStatic = it.isJavaStaticMethod() |
| ) |
| errorContextInjector(it) { |
| functionToSummaryConverter(it, modifierHints) |
| } |
| } |
| |
| return DefaultSummaryList( |
| SummaryList.Params( |
| header = name?.let { |
| DefaultTableTitle( |
| TableTitle.Params( |
| title = name, |
| big = true |
| ) |
| ) |
| }, |
| items = components |
| ) |
| ) |
| } |
| |
| private fun constructorsToSummary(name: String, constructors: List<DFunction>): |
| ConstructorSummaryList { |
| val components = constructors.mapNotNull { |
| errorContextInjector(it) { |
| constructorToSummaryConverter(it) |
| } |
| } |
| |
| return DefaultSummaryList( |
| SummaryList.Params( |
| header = DefaultTableTitle( |
| TableTitle.Params( |
| title = name, |
| big = true |
| ) |
| ), |
| items = components |
| ) |
| ) |
| } |
| |
| private fun functionsToDetail(functions: List<DFunction>): |
| List<SymbolDetail<FunctionSignature>> { |
| return functions.mapNotNull { |
| val modifierHints = ModifierHints( |
| displayLanguage = displayLanguage, |
| isSummary = false, |
| type = DFunction::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| injectStatic = it.isJavaStaticMethod() |
| ) |
| errorContextInjector(it) { |
| functionToDetailConverter(it, modifierHints) |
| } |
| } |
| } |
| |
| private fun constructorsToDetail(functions: List<DFunction>): |
| List<SymbolDetail<FunctionSignature>> { |
| val modifierHints = ModifierHints( |
| displayLanguage = displayLanguage, |
| type = DFunction::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| isSummary = false, |
| isConstructor = true |
| ) |
| return functions.mapNotNull { |
| errorContextInjector(it) { |
| constructorToDetailConverter(it, modifierHints) |
| } |
| } |
| } |
| |
| private fun enumValuesToSummary(title: String, enumVals: List<DEnumEntry>): |
| LinkDescriptionSummaryList { |
| val components = enumVals.map { errorContextInjector(it) { enumConverter.summary(it) } } |
| return DefaultSummaryList( |
| SummaryList.Params( |
| header = DefaultTableTitle( |
| TableTitle.Params( |
| title = title, |
| big = true |
| ) |
| ), |
| items = components |
| ) |
| ) |
| } |
| |
| private fun propertiesToSummary(name: String? = null, properties: List<DProperty>): |
| PropertySummaryList { |
| val components = properties.mapNotNull { |
| val modifierHints = ModifierHints( |
| displayLanguage = displayLanguage, |
| type = DProperty::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| isSummary = true, |
| injectStatic = it.isJavaStaticField() |
| ) |
| errorContextInjector(it) { |
| propertyToSummaryConverter(it, modifierHints) |
| } |
| } |
| |
| val title = name?.let { |
| DefaultTableTitle( |
| TableTitle.Params( |
| title = it, |
| big = true |
| ) |
| ) |
| } |
| |
| return DefaultSummaryList( |
| SummaryList.Params( |
| header = title, |
| items = components |
| ) |
| ) |
| } |
| |
| private fun propertiesToDetail(properties: List<DProperty>): |
| List<SymbolDetail<PropertySignature>> { |
| return properties.mapNotNull { |
| val modifierHints = ModifierHints( |
| displayLanguage = displayLanguage, |
| type = DProperty::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| isSummary = false, |
| injectStatic = it.isJavaStaticField() |
| ) |
| errorContextInjector(it) { |
| propertyToDetailConverter(it, modifierHints) |
| } |
| } |
| } |
| |
| private fun enumValuesToDetail(dEnum: DEnum, enumValues: List<DEnumEntry>): |
| List<SymbolDetail<PropertySignature>> { |
| val modifierHints = ModifierHints( |
| displayLanguage, |
| type = DEnumEntry::class.java, |
| containingType = classlike::class.java, |
| isFromJava = classlike.isFromJava(), |
| isSummary = false |
| ) |
| return enumValues.map { |
| errorContextInjector(it) { |
| enumConverter.detail(dEnum, it, modifierHints) |
| } |
| } |
| } |
| |
| // To allow consolidating identical ClasslikeSignatures generated from different sourceSets, |
| // we want all identical ClasslikeSignatures to be ==. Because we can't rely on == for upstream |
| // data classes like DClasslike, we need a workaround. We abstract out the inputs to this fun. |
| private data class SourceSetDependentSignatureInputs( |
| val modifiers: Modifiers, |
| val annotations: List<Annotations.Annotation>, |
| val typeAliasEquals: TypeProjectionComponent? = null |
| ) |
| private data class SourceSetIndependentSignatureInputs( |
| val generics: List<DTypeParameter>, |
| val name: String, |
| val dri: DRI, |
| val type: String, |
| val isFromJava: Boolean |
| ) |
| private val SIGNATURE_INSTANCES = concurrentMapOf< |
| Pair<SourceSetDependentSignatureInputs, SourceSetIndependentSignatureInputs>, |
| ClasslikeSignature>() |
| |
| protected open fun computeSignature( |
| classlike: DClasslike, |
| classGraph: ClassGraph, |
| sourceSet: DokkaSourceSet = classlike.getExpectOrCommonSourceSet(), |
| typealiasEquals: Bound? = null |
| ): ClasslikeSignature { |
| val sourceSetDependentInput = computeSourceSetDependentSignatureInputs(classlike, sourceSet) |
| val sourceSetIndepInput = computeSourceSetIndependentSignatureInputs(classlike) |
| |
| return SIGNATURE_INSTANCES.getOrPut( |
| sourceSetDependentInput to sourceSetIndepInput |
| ) { |
| // TODO(KMP ClassGraph b/253454963. Move these into sourceSetDependentInput.) |
| val (extends, implements) = when (sourceSetIndepInput.type) { |
| "class", "interface", "enum", "object" -> ( |
| classGraph.getValue(sourceSetIndepInput.dri).directSuperClasses.map { |
| pathProvider.linkForReference(it.dri) |
| } to classGraph.getValue(sourceSetIndepInput.dri).directInterfaces.map { |
| pathProvider.linkForReference(it.dri) |
| } |
| ) |
| else -> emptyList<Link>() to emptyList<Link>() |
| } |
| DefaultClasslikeSignature( |
| ClasslikeSignature.Params( |
| displayLanguage = displayLanguage, |
| name = pathProvider |
| .linkForReference(sourceSetIndepInput.dri, sourceSetIndepInput.name), |
| type = if (sourceSetDependentInput.typeAliasEquals != null) "typealias" |
| else sourceSetIndepInput.type, |
| modifiers = sourceSetDependentInput.modifiers, |
| implements = implements, |
| extends = extends, |
| typeParameters = sourceSetIndepInput.generics.map { |
| errorContextInjector(it) { |
| paramConverter.componentForTypeParameter( |
| it, |
| sourceSetIndepInput.isFromJava |
| ) |
| } |
| }, |
| annotationComponents = annotationConverter.annotationComponents( |
| annotations = sourceSetDependentInput.annotations, |
| nullability = Nullability.DONT_CARE, // Classlike definitions aren't null |
| ), |
| typeAliasEquals = sourceSetDependentInput.typeAliasEquals |
| ) |
| ) |
| } |
| } |
| |
| private fun computeSourceSetDependentSignatureInputs( |
| classlike: DClasslike, |
| sourceSet: DokkaSourceSet |
| ): SourceSetDependentSignatureInputs { |
| val typeAliasEquals = (classlike as? WithExtraProperties<*>)?.extra |
| ?.allOfType<ActualTypealias>()?.strictSingleOrNull()?.underlyingType?.get(sourceSet) |
| val modifiers = classlike.modifiers(sourceSet) + |
| (typeAliasEquals?.let { listOf("actual") } ?: emptyList()) |
| return SourceSetDependentSignatureInputs( |
| modifiers.modifiersFor( |
| ModifierHints( |
| displayLanguage, |
| type = classlike::class.java, |
| containingType = null, |
| injectStatic = classlike is DObject && displayLanguage == Language.JAVA, |
| isFromJava = classlike.isFromJava(), |
| isSummary = false |
| ) |
| ), |
| classlike.annotations(sourceSet), |
| typeAliasEquals?.let { paramConverter.componentForProjection(it, false, sourceSet) } |
| ) |
| } |
| private fun computeSourceSetIndependentSignatureInputs( |
| classlike: DClasslike |
| ) = SourceSetIndependentSignatureInputs( |
| dri = classlike.dri, |
| name = classlike.name(), |
| type = classlike.stringForType(displayLanguage), |
| generics = classlike.generics(), |
| isFromJava = classlike.isFromJava() |
| ) |
| |
| /** Walks up this class' type hierarchy and returns the hierarchy component. */ |
| protected suspend fun computeHierarchy(): ClassHierarchy { |
| if (classlike !is WithSupertypes) { |
| return DefaultClassHierarchy(ClassHierarchy.Params(parents = emptyList())) |
| } |
| |
| val parents = docsHolder.classGraph().getValue(classlike.dri).superClasses |
| if (parents.isEmpty()) { |
| // Don't show the hierarchy if this class only extends Any/Object |
| return DefaultClassHierarchy(ClassHierarchy.Params(parents = emptyList())) |
| } |
| |
| val classHierarchyRootDri = when (displayLanguage) { |
| Language.JAVA -> DRI(packageName = "java.lang", classNames = "Object") |
| Language.KOTLIN -> DRI(packageName = "kotlin", classNames = "Any") |
| } |
| val classHierarchyRootLink = pathProvider |
| .linkForReference(classHierarchyRootDri, classHierarchyRootDri.fullName) |
| val thisLink = pathProvider.linkForReference(classlike.dri, classlike.dri.fullName) |
| |
| val parentLinks = parents.map { classlike -> |
| pathProvider.linkForReference(classlike.dri, classlike.dri.fullName) |
| } |
| val allLinks = listOf(classHierarchyRootLink) + parentLinks + listOf(thisLink) |
| return DefaultClassHierarchy(ClassHierarchy.Params(allLinks)) |
| } |
| |
| /** |
| * Creates a list of InheritedSymbols from a list of DFunctions |
| */ |
| private fun computeInheritedSymbols( |
| symbolList: List<Documentable> |
| ): Triple< |
| InheritedSymbolsList<FunctionSignature>?, |
| InheritedSymbolsList<PropertySignature>?, |
| InheritedSymbolsList<PropertySignature>? |
| > { |
| val symbols = when (displayLanguage) { |
| Language.JAVA -> symbolList.filterOutJvmSynthetic() |
| Language.KOTLIN -> symbolList |
| } |
| |
| val functions = symbols.filterIsInstance<DFunction>() |
| .sortedWith(functionSignatureComparator()) |
| val functionsRenamed = when (displayLanguage) { |
| Language.JAVA -> functions.map { it.withJvmName() } |
| Language.KOTLIN -> functions |
| } |
| val functionsSummary = |
| functionsRenamed.takeIf { it.isNotEmpty() } |
| ?.createInheritedCategory(title = inheritedMethodsTitle(displayLanguage)) { |
| functionsToSummary(functions = it) |
| } |
| |
| val (consts, properties) = symbols.filterIsInstance<DProperty>() |
| .sortedBy { "${it.name} ${it.dri}" } |
| .partition { it.isConstant() } |
| |
| val constsSummary = consts.takeIf { it.isNotEmpty() } |
| ?.createInheritedCategory(title = inheritedConstantsTitle()) { |
| propertiesToSummary(properties = it) |
| } |
| |
| val propertiesSummary = properties.takeIf { it.isNotEmpty() } |
| ?.createInheritedCategory(title = inheritedPropertiesTitle(displayLanguage)) { |
| propertiesToSummary(properties = it) |
| } |
| |
| return Triple(functionsSummary, constsSummary, propertiesSummary) |
| } |
| |
| private fun <T, U : SymbolSignature> List<T>.createInheritedCategory( |
| title: String, |
| summaryGen: (List<T>) -> SummaryList<TypeSummaryItem<U>> |
| ): InheritedSymbolsList<U> where T : Documentable, T : WithExtraProperties<T> { |
| fun createInheritedSymbolsList(parentDri: DRI, symbolList: List<T>): |
| Pair<Link, SummaryList<TypeSummaryItem<U>>> { |
| val link = pathProvider.linkForReference(parentDri, parentDri.fullName) |
| val summary = summaryGen(symbolList) |
| return link to summary |
| } |
| |
| // val category = groupBy { it.driInheritedFrom() ?: it.dri.parent } |
| val category = groupBy { it.dri.parent } |
| .toSortedMap(compareBy { it.classNames + " " + it.fullName }) |
| .entries.associate { (k, v) -> createInheritedSymbolsList(k, v) } |
| |
| return DefaultInheritedSymbols( |
| InheritedSymbolsList.Params( |
| header = DefaultTableTitle( |
| TableTitle.Params(title, big = true) |
| ), |
| inheritedSymbolSummaries = category |
| ) |
| ) |
| } |
| |
| /** |
| * WARNING: does not work properly |
| * The dri from which this documentable is inherited, or null. |
| * |
| * `extra[InheritedMember].inheritedFrom` does not actually contain where inherited members are |
| * inherited from |
| */ /* |
| private fun <T> T.driInheritedFrom(): DRI? |
| where T : Documentable, T : WithExtraProperties<T> = |
| extra[InheritedMember]?.inheritedFrom?.values?.toSet()?.singleOrNull() */ |
| |
| /** Finds the direct and indirect subclasses for this classlike, returning their component. */ |
| // We know our subclasses will always be DClasslikes |
| protected suspend fun findRelatedSymbols(): RelatedSymbols { |
| val classNode = docsHolder.classGraph().getValue(classlike.dri) |
| val directSubclasses = classNode.directSubClasses |
| val indirectSubclasses = classNode.indirectSubClasses |
| |
| return DefaultRelatedSymbols( |
| RelatedSymbols.Params( |
| // TODO(KMP; b/254490320) |
| directSubclasses = linksForClasslikes(directSubclasses), |
| directSummary = javadocConverter.docsToSummaryDefault(directSubclasses), |
| indirectSubclasses = linksForClasslikes(indirectSubclasses), |
| indirectSummary = javadocConverter.docsToSummaryDefault(indirectSubclasses) |
| ) |
| ) |
| } |
| |
| /** Converts the classlikes to link components for use in the related symbols component. */ |
| private fun linksForClasslikes(docs: List<DClasslike>): List<Link> { |
| return docs.map { pathProvider.linkForReference(it.dri) } |
| } |
| |
| /** |
| * Returns lists of functions and properties directly owned by this class-like. |
| */ |
| private suspend fun DClasslike.nonInheritedTypes(): Pair<List<DFunction>, List<DProperty>> { |
| val allFunctions = if (displayLanguage == Language.KOTLIN) this.functions |
| else this.functions + this.properties.gettersAndSetters() |
| |
| val supertypes = this.supertypesForDisplayLanguage() |
| |
| return Pair( |
| allFunctions.nonInheritedTypes(supertypes, this), |
| this.properties.nonInheritedTypes(supertypes, this) |
| ) |
| } |
| |
| /** |
| * Returns the list of declared symbols. That is, symbols directly owned by the provided |
| * class-like and not found through the inheritance hierarchy. |
| * |
| * Class and package comparison isn't applicable for synthetic classes. |
| */ |
| private inline fun <reified T : Documentable> List<T>.nonInheritedTypes( |
| supertypes: Set<DRI>, |
| forClass: DClasslike, |
| ): List<T> { |
| if (forClass.isSynthetic) { |
| return this |
| } |
| return filter { symbol -> !symbol.isInherited(supertypes) && !symbol.dri.isFromBaseClass() } |
| .map { symbol -> symbol.withDRIOfClass(forClass) } |
| } |
| |
| /** |
| * If the DProperty or DFunction does not already have a DRI with the given class, makes a copy |
| * of it with a new DRI. Defined over Documentables because both DProperty and Function have a |
| * `copy` method defined because they are data classes, but there isn't a way to specify the |
| * Documentable must be a data class. |
| */ |
| private inline fun <reified T : Documentable> T.withDRIOfClass(forClass: DClasslike): T { |
| return if (forClass.packageName() == this.dri.packageName && |
| forClass.name() == this.dri.classNames |
| ) { |
| this |
| } else { |
| val dri = this.dri.copy( |
| packageName = forClass.packageName(), classNames = forClass.name() |
| ) |
| when (this) { |
| is DFunction -> this.copy(dri) as T |
| is DProperty -> this.copy(dri) as T |
| else -> throw RuntimeException() |
| } |
| } |
| } |
| |
| /** |
| * Gather superclasses and interfaces for this class, converting mapped types when applicable. |
| */ |
| private suspend fun DClasslike.supertypesForDisplayLanguage(): Set<DRI> { |
| val classNode = docsHolder.classGraph()[this.dri] ?: return emptySet() |
| return (classNode.superClasses + classNode.interfaces) |
| .map { it.dri.possiblyConvertMappedType(displayLanguage) } |
| .toSet() |
| } |
| |
| private suspend fun DClasslike.companionFunctionsAndProperties(): |
| Pair<List<DFunction>, List<DProperty>> { |
| return (this as? WithCompanion)?.companion?.nonInheritedTypes() |
| ?: return Pair(emptyList(), emptyList()) |
| } |
| |
| /** |
| * Returns the list of inherited symbols, not from Any or Object |
| * If class is synthetic there should be no inherited methods |
| */ |
| private fun <T : Documentable> List<T>.inheritedTypes(supertypes: Set<DRI>): List<T> { |
| if (classlike.isSynthetic) { |
| return emptyList() |
| } |
| return filter { symbol -> symbol.isInherited(supertypes) && !symbol.dri.isFromBaseClass() } |
| } |
| |
| private fun <T : Documentable> T.isInherited(supertypes: Set<DRI>): Boolean { |
| // Convert mapped type for the function to make sure the DRI lines up with supertypes. |
| val classDRI = DRI(dri.packageName, dri.classNames) |
| .possiblyConvertMappedType(displayLanguage) |
| return supertypes.contains(classDRI) |
| } |
| |
| private fun createDefaultConstructorFor(classlike: DClasslike) = |
| DFunction( |
| dri = classlike.dri.copy(callable = Callable(classlike.name!!, null, emptyList())), |
| name = classlike.name!!, |
| isConstructor = true, |
| parameters = emptyList(), |
| documentation = emptyMap(), |
| expectPresentInSet = null, |
| sources = emptyMap(), |
| visibility = classlike.visibility, |
| type = GenericTypeConstructor(dri = classlike.dri, projections = emptyList()), |
| generics = emptyList(), |
| receiver = null, |
| modifier = classlike.visibility.keys.associateWith { KotlinModifier.Final }, |
| sourceSets = classlike.sourceSets, |
| isExpectActual = false |
| ) |
| |
| protected open fun <I : Documentable, O> errorContextInjector( |
| documentable: I, |
| sourceSet: DokkaSourceSet = documentable.getExpectOrCommonSourceSet(), |
| toDo: (I) -> O, |
| ): O { |
| try { |
| return toDo(documentable) |
| } catch (e: Exception) { |
| var message = "Error when handling ${documentable::class} ${documentable.name} " + |
| "in ${classlike.name}" |
| if (documentable is WithSources) |
| message += ", " + documentable.getErrorLocation(sourceSet) |
| throw RuntimeException(message, e) |
| } |
| } |
| } |
| |
| /** |
| * Returns whether this DObject is an ordinary companion and is not signifcant enough to show on its |
| * own. This requires that it be not named, not inherit anything, and *not contain anything that is |
| * not hoisted onto the containing object*. |
| * |
| * This is a best-effort temporary approximation. Even as-Java, companions can be fully hoistable. |
| * |
| * This function can also be used on DObjects where it is unknown whether it is a companion at all. |
| * This works because we enforce non-companion objects being named 'Companion' as an error. |
| * |
| * Returns true: is both a companion and uninteresting |
| * Returns false: either is not a companion, or is interesting |
| */ |
| internal fun DObject.isOrdinaryCompanion(): Boolean = |
| name == "Companion" && |
| supertypes.all { it.value.isEmpty() } && |
| children.none { it is DClasslike } |
| |
| internal fun DClasslike.shouldNotBeDisplayed(displayLanguage: Language) = |
| displayLanguage == Language.KOTLIN && this is DObject && this.isOrdinaryCompanion() |
| |
| // an `actual` cannot narrow visibility, but can widen it TODO(b/262710702) |
| private fun isPublic(element: Documentable) = |
| "public" in element.modifiers(element.getExpectOrCommonSourceSet()) |
| private fun isProtected(element: Documentable) = |
| "protected" in element.modifiers(element.getExpectOrCommonSourceSet()) |
| /** Filter out constants, because those have a separate display sections from properties. */ |
| private fun isPublicNonConst(prop: DProperty) = isPublic(prop) && !prop.isConstant() |
| private fun isProtectedNonConst(prop: DProperty) = isProtected(prop) && !prop.isConstant() |
| |
| /** |
| * Returns true if function is a suspend function itself, or takes a suspend function as a |
| * parameter. |
| */ |
| private fun DFunction.isSuspendFunction() = |
| type.isSuspend() || parameters.any { it.type.isSuspend() } |
| |
| private fun List<DProperty>.constants() = filter { it.isConstant() }.toSet().toList() |
| |
| internal fun nestedTypesTitle() = "Nested types" |
| |
| internal fun publicConstructorsTitle() = "Public ${constructorsTitle()}" |
| internal fun protectedConstructorsTitle() = "Protected ${constructorsTitle()}" |
| internal fun constructorsTitle(): String = "constructors" |
| |
| internal fun publicMethodsTitle(displayLanguage: Language) = |
| "Public ${methodsTitle(displayLanguage)}" |
| internal fun inheritedMethodsTitle(displayLanguage: Language) = |
| "Inherited ${methodsTitle(displayLanguage)}" |
| internal fun protectedMethodsTitle(displayLanguage: Language) = |
| "Protected ${methodsTitle(displayLanguage)}" |
| internal fun methodsTitle(displayLanguage: Language): String = when (displayLanguage) { |
| Language.JAVA -> "methods" |
| Language.KOTLIN -> "functions" |
| } |
| |
| internal fun publicPropertiesTitle(displayLanguage: Language) = |
| "Public ${propertiesTitle(displayLanguage)}" |
| internal fun inheritedPropertiesTitle(displayLanguage: Language) = |
| "Inherited ${propertiesTitle(displayLanguage)}" |
| internal fun protectedPropertiesTitle(displayLanguage: Language) = |
| "Protected ${propertiesTitle(displayLanguage)}" |
| internal fun propertiesTitle(displayLanguage: Language): String = when (displayLanguage) { |
| Language.JAVA -> "fields" |
| Language.KOTLIN -> "properties" |
| } |
| |
| internal fun constantsTitle() = "Constants" |
| internal fun inheritedConstantsTitle() = "Inherited ${constantsTitle()}" |
| internal fun enumValuesTitle() = "Enum Values" |
| // Extension functions and companions are a Kotlin-only feature and only show up in as-Kotlin |
| internal fun extensionFunctionsTitle() = "Extension functions" |
| internal fun extensionPropertiesTitle() = "Extension properties" |
| internal fun companionFunctionsTitle(): String = "companion ${methodsTitle(Language.KOTLIN)}" |
| internal fun companionPropertiesTitle(): String = "companion ${propertiesTitle(Language.KOTLIN)}" |
| internal fun publicCompanionFunctionsTitle(): String = "Public ${companionFunctionsTitle()}" |
| internal fun protectedCompanionFunctionsTitle(): String = "Protected ${companionFunctionsTitle()}" |
| internal fun publicCompanionPropertiesTitle(): String = "Public ${companionPropertiesTitle()}" |
| internal fun protectedCompanionPropertiesTitle(): String = "Protected ${companionPropertiesTitle()}" |