aboutsummaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/kotlin/Analysis/AnalysisEnvironment.kt267
-rw-r--r--core/src/main/kotlin/Analysis/CoreKotlinCacheService.kt30
-rw-r--r--core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt30
-rw-r--r--core/src/main/kotlin/Analysis/JavaResolveExtension.kt131
-rw-r--r--core/src/main/kotlin/DokkaBootstrapImpl.kt30
-rw-r--r--core/src/main/kotlin/Formats/AnalysisComponents.kt45
-rw-r--r--core/src/main/kotlin/Formats/FormatDescriptor.kt48
-rw-r--r--core/src/main/kotlin/Formats/GFMFormatService.kt46
-rw-r--r--core/src/main/kotlin/Formats/HtmlFormatService.kt23
-rw-r--r--core/src/main/kotlin/Formats/HtmlTemplateService.kt8
-rw-r--r--core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt117
-rw-r--r--core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt91
-rw-r--r--core/src/main/kotlin/Formats/JekyllFormatService.kt26
-rw-r--r--core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt67
-rw-r--r--core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt41
-rw-r--r--core/src/main/kotlin/Formats/MarkdownFormatService.kt14
-rw-r--r--core/src/main/kotlin/Formats/OutlineService.kt2
-rw-r--r--core/src/main/kotlin/Formats/PackageListService.kt10
-rw-r--r--core/src/main/kotlin/Formats/StandardFormats.kt44
-rw-r--r--core/src/main/kotlin/Formats/StructuredFormatService.kt381
-rw-r--r--core/src/main/kotlin/Formats/YamlOutlineService.kt8
-rw-r--r--core/src/main/kotlin/Generation/ConsoleGenerator.kt42
-rw-r--r--core/src/main/kotlin/Generation/DocumentationMerger.kt180
-rw-r--r--core/src/main/kotlin/Generation/DokkaGenerator.kt99
-rw-r--r--core/src/main/kotlin/Generation/FileGenerator.kt32
-rw-r--r--core/src/main/kotlin/Generation/Generator.kt8
-rw-r--r--core/src/main/kotlin/Generation/configurationImpl.kt75
-rw-r--r--core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt59
-rw-r--r--core/src/main/kotlin/Java/JavadocParser.kt340
-rw-r--r--core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt13
-rw-r--r--core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt34
-rw-r--r--core/src/main/kotlin/Kotlin/DocumentationBuilder.kt486
-rw-r--r--core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt171
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt22
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt2
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt25
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt9
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt34
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinLanguageService.kt201
-rw-r--r--core/src/main/kotlin/Languages/CommonLanguageService.kt84
-rw-r--r--core/src/main/kotlin/Languages/NewJavaLanguageService.kt197
-rw-r--r--core/src/main/kotlin/Locations/FoldersLocationService.kt30
-rw-r--r--core/src/main/kotlin/Locations/Location.kt61
-rw-r--r--core/src/main/kotlin/Locations/LocationService.kt78
-rw-r--r--core/src/main/kotlin/Locations/SingleFolderLocationService.kt20
-rw-r--r--core/src/main/kotlin/Model/Content.kt33
-rw-r--r--core/src/main/kotlin/Model/DescriptorSignatureProvider.kt7
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt86
-rw-r--r--core/src/main/kotlin/Model/DocumentationReference.kt67
-rw-r--r--core/src/main/kotlin/Model/ElementSignatureProvider.kt9
-rw-r--r--core/src/main/kotlin/Model/PackageDocs.kt69
-rw-r--r--core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt2
-rw-r--r--core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt4
-rw-r--r--core/src/main/kotlin/Utilities/DokkaModules.kt86
-rw-r--r--core/src/main/kotlin/Utilities/Html.kt4
-rw-r--r--core/src/main/kotlin/Utilities/ServiceLocator.kt18
-rw-r--r--core/src/main/kotlin/Utilities/Uri.kt40
-rw-r--r--core/src/main/kotlin/javadoc/dokka-adapters.kt32
-rw-r--r--core/src/main/resources/dokka/format/java-layout-html.properties2
-rw-r--r--core/src/main/resources/dokka/format/javadoc.properties3
-rw-r--r--core/src/main/resources/dokka/generator/default.properties2
-rw-r--r--core/src/main/resources/dokka/generator/javadoc.properties2
-rw-r--r--core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties2
-rw-r--r--core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties2
-rw-r--r--core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties2
-rw-r--r--core/src/main/resources/dokka/language/java.properties1
-rw-r--r--core/src/main/resources/dokka/language/kotlin.properties1
-rw-r--r--core/src/main/resources/dokka/outline/yaml.properties1
68 files changed, 3058 insertions, 1178 deletions
diff --git a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt
index 56249ac4..fe9ec2fa 100644
--- a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt
+++ b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt
@@ -1,5 +1,6 @@
package org.jetbrains.dokka
+import com.google.common.collect.ImmutableMap
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.core.CoreModuleManager
import com.intellij.mock.MockComponentManager
@@ -17,6 +18,12 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.io.URLUtil
import org.jetbrains.kotlin.analyzer.*
+import org.jetbrains.kotlin.analyzer.common.CommonAnalyzerFacade
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns
+import org.jetbrains.kotlin.caches.project.LibraryModuleInfo
+import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
@@ -27,23 +34,28 @@ import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.container.getService
+import org.jetbrains.kotlin.container.tryGetService
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.PackagePartProvider
+import org.jetbrains.kotlin.idea.caches.resolve.JsAnalyzerFacade
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.js.config.JSConfigurationKeys
+import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.platform.JvmBuiltIns
-import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.CompilerEnvironment
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.resolve.*
+import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.jvm.JvmAnalyzerFacade
import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
+import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import java.io.File
/**
@@ -54,7 +66,7 @@ import java.io.File
* $messageCollector: required by compiler infrastructure and will receive all compiler messages
* $body: optional and can be used to configure environment without creating local variable
*/
-class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
+class AnalysisEnvironment(val messageCollector: MessageCollector, val analysisPlatform: Platform) : Disposable {
val configuration = CompilerConfiguration()
init {
@@ -63,7 +75,12 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
fun createCoreEnvironment(): KotlinCoreEnvironment {
System.setProperty("idea.io.use.fallback", "true")
- val environment = KotlinCoreEnvironment.createForProduction(this, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
+
+ val configFiles = when (analysisPlatform) {
+ Platform.jvm, Platform.common -> EnvironmentConfigFiles.JVM_CONFIG_FILES
+ Platform.js -> EnvironmentConfigFiles.JS_CONFIG_FILES
+ }
+ val environment = KotlinCoreEnvironment.createForProduction(this, configuration, configFiles)
val projectComponentManager = environment.project as MockComponentManager
val projectFileIndex = CoreProjectFileIndex(environment.project,
@@ -87,19 +104,33 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
return environment
}
- fun createSourceModuleSearchScope(project: Project, sourceFiles: List<KtFile>): GlobalSearchScope {
- // TODO: Fix when going to implement dokka for JS
- return TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles)
- }
+ fun createSourceModuleSearchScope(project: Project, sourceFiles: List<KtFile>): GlobalSearchScope =
+ when (analysisPlatform) {
+ Platform.jvm -> TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles)
+ Platform.js, Platform.common -> GlobalSearchScope.filesScope(project, sourceFiles.map { it.virtualFile }.toSet())
+ }
- fun createResolutionFacade(environment: KotlinCoreEnvironment): DokkaResolutionFacade {
+ fun createResolutionFacade(environment: KotlinCoreEnvironment): Pair<DokkaResolutionFacade, DokkaResolutionFacade> {
val projectContext = ProjectContext(environment.project)
val sourceFiles = environment.getSourceFiles()
- val library = object : ModuleInfo {
+ val targetPlatform = when (analysisPlatform) {
+ Platform.js -> JsPlatform
+ Platform.common -> TargetPlatform.Common
+ Platform.jvm -> JvmPlatform
+ }
+
+ val library = object : LibraryModuleInfo {
+ override val platform: TargetPlatform
+ get() = targetPlatform
+
+ override fun getLibraryRoots(): Collection<String> {
+ return classpath.map { it.absolutePath }
+ }
+
override val name: Name = Name.special("<library>")
override fun dependencies(): List<ModuleInfo> = listOf(this)
}
@@ -109,56 +140,126 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
}
val sourcesScope = createSourceModuleSearchScope(environment.project, sourceFiles)
+ val modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo> = {
+ when (it) {
+ library -> ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope))
+ module -> ModuleContent(it, emptyList(), GlobalSearchScope.allScope(environment.project))
+ else -> throw IllegalArgumentException("Unexpected module info")
+ }
+ }
- val builtIns = JvmBuiltIns(projectContext.storageManager)
+ var builtIns: JvmBuiltIns? = null
+ val resolverForProject = when (analysisPlatform) {
+ Platform.jvm -> {
+ builtIns = JvmBuiltIns(projectContext.storageManager)
+ createJvmResolverForProject(projectContext, module, library, modulesContent, sourcesScope, builtIns)
+ }
+ Platform.js -> createJsResolverForProject(projectContext, module, library, modulesContent)
+ Platform.common -> createCommonResolverForProject(projectContext, module, library, modulesContent, environment)
- val javaRoots = classpath
- .mapNotNull {
- val rootFile = when {
- it.extension == "jar" ->
- StandardFileSystems.jar().findFileByPath("${it.absolutePath}${URLUtil.JAR_SEPARATOR}")
- else ->
- StandardFileSystems.local().findFileByPath(it.absolutePath)
- }
+ }
+ val resolverForLibrary = resolverForProject.resolverForModule(library) // Required before module to initialize library properly
+ val resolverForModule = resolverForProject.resolverForModule(module)
+ val libraryModuleDescriptor = resolverForProject.descriptorForModule(library)
+ val moduleDescriptor = resolverForProject.descriptorForModule(module)
+ builtIns?.initialize(moduleDescriptor, true)
+ val libraryResolutionFacade = DokkaResolutionFacade(environment.project, libraryModuleDescriptor, resolverForLibrary)
+ val created = DokkaResolutionFacade(environment.project, moduleDescriptor, resolverForModule)
+ val projectComponentManager = environment.project as MockComponentManager
+ projectComponentManager.registerService(KotlinCacheService::class.java, CoreKotlinCacheService(created))
- rootFile?.let { JavaRoot(it, JavaRoot.RootType.BINARY) }
- }
+ return created to libraryResolutionFacade
+ }
- val resolverForProject = ResolverForProjectImpl(
- "Dokka",
- projectContext,
- listOf(library, module),
- { JvmAnalyzerFacade },
- {
- when (it) {
- library -> ModuleContent(emptyList(), GlobalSearchScope.notScope(sourcesScope))
- module -> ModuleContent(sourceFiles, sourcesScope)
- else -> throw IllegalArgumentException("Unexpected module info")
- }
- },
- JvmPlatformParameters {
- val file = (it as JavaClassImpl).psi.containingFile.virtualFile
- if (file in sourcesScope)
- module
- else
- library
- },
- CompilerEnvironment,
- packagePartProviderFactory = { info, content ->
- JvmPackagePartProvider(configuration.languageVersionSettings, content.moduleContentScope).apply {
- addRoots(javaRoots)
- }
- },
- builtIns = builtIns,
- modulePlatforms = { JvmPlatform.multiTargetPlatform }
+ private fun createCommonResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ library: LibraryModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>,
+ environment: KotlinCoreEnvironment
+ ): ResolverForProjectImpl<ModuleInfo> {
+ return ResolverForProjectImpl(
+ debugName = "Dokka",
+ projectContext = projectContext,
+ modules = listOf(module, library),
+ modulesContent = modulesContent,
+ modulePlatforms = { MultiTargetPlatform.Common },
+ moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */,
+ resolverForModuleFactoryByPlatform = { CommonAnalyzerFacade },
+ platformParameters = object : PlatformAnalysisParameters {},
+ targetEnvironment = CompilerEnvironment,
+ packagePartProviderFactory = { content ->
+ environment.createPackagePartProvider(content.moduleContentScope)
+ },
+ builtIns = DefaultBuiltIns.Instance
)
+ }
- resolverForProject.resolverForModule(library) // Required before module to initialize library properly
- val resolverForModule = resolverForProject.resolverForModule(module)
- val moduleDescriptor = resolverForProject.descriptorForModule(module)
- builtIns.initialize(moduleDescriptor, true)
- return DokkaResolutionFacade(environment.project, moduleDescriptor, resolverForModule)
+ private fun createJsResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ library: LibraryModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>
+ ): ResolverForProjectImpl<ModuleInfo> {
+ return ResolverForProjectImpl(
+ debugName = "Dokka",
+ projectContext = projectContext,
+ modules = listOf(module, library),
+ modulesContent = modulesContent,
+ modulePlatforms = { JsPlatform.multiTargetPlatform },
+ moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */,
+ resolverForModuleFactoryByPlatform = { JsAnalyzerFacade },
+ platformParameters = object : PlatformAnalysisParameters {},
+ targetEnvironment = CompilerEnvironment,
+ packagePartProviderFactory = { PackagePartProvider.Empty },
+ builtIns = JsPlatform.builtIns
+ )
+ }
+
+ private fun createJvmResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ library: LibraryModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>,
+ sourcesScope: GlobalSearchScope,
+ builtIns: KotlinBuiltIns
+ ): ResolverForProjectImpl<ModuleInfo> {
+ val javaRoots = classpath
+ .mapNotNull {
+ val rootFile = when {
+ it.extension == "jar" ->
+ StandardFileSystems.jar().findFileByPath("${it.absolutePath}${URLUtil.JAR_SEPARATOR}")
+ else ->
+ StandardFileSystems.local().findFileByPath(it.absolutePath)
+ }
+
+ rootFile?.let { JavaRoot(it, JavaRoot.RootType.BINARY) }
+ }
+
+ return ResolverForProjectImpl(
+ debugName = "Dokka",
+ projectContext = projectContext,
+ modules = listOf(module, library),
+ modulesContent = modulesContent,
+ modulePlatforms = { JvmPlatform.multiTargetPlatform },
+ moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */,
+ resolverForModuleFactoryByPlatform = { JvmAnalyzerFacade },
+ platformParameters = JvmPlatformParameters {
+ val file = (it as JavaClassImpl).psi.containingFile.virtualFile
+ if (file in sourcesScope)
+ module
+ else
+ library
+ },
+ targetEnvironment = CompilerEnvironment,
+ packagePartProviderFactory = { content ->
+ JvmPackagePartProvider(configuration.languageVersionSettings, content.moduleContentScope).apply {
+ addRoots(javaRoots)
+ }
+ },
+ builtIns = builtIns
+ )
}
fun loadLanguageVersionSettings(languageVersionString: String?, apiVersionString: String?) {
@@ -178,6 +279,10 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
* $paths: collection of files to add
*/
fun addClasspath(paths: List<File>) {
+ if (analysisPlatform == Platform.js) {
+ configuration.addAll(JSConfigurationKeys.LIBRARIES, paths.map { it.absolutePath })
+ }
+ configuration.addAll(JSConfigurationKeys.LIBRARIES, paths.map { it.absolutePath })
configuration.addJvmClasspathRoots(paths)
}
@@ -186,6 +291,10 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
* $path: path to add
*/
fun addClasspath(path: File) {
+ if (analysisPlatform == Platform.js) {
+ configuration.add(JSConfigurationKeys.LIBRARIES, path.absolutePath)
+ }
+ configuration.add(JSConfigurationKeys.LIBRARIES, path.absolutePath)
configuration.addJvmClasspathRoot(path)
}
@@ -232,8 +341,12 @@ fun contentRootFromPath(path: String): ContentRoot {
class DokkaResolutionFacade(override val project: Project,
override val moduleDescriptor: ModuleDescriptor,
val resolverForModule: ResolverForModule) : ResolutionFacade {
+ override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult {
+ throw UnsupportedOperationException()
+ }
+
override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? {
- return null
+ return resolverForModule.componentProvider.tryGetService(serviceClass)
}
override fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode): DeclarationDescriptor {
@@ -247,10 +360,42 @@ class DokkaResolutionFacade(override val project: Project,
val resolveSession: ResolveSession get() = getFrontendService(ResolveSession::class.java)
override fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode): BindingContext {
- throw UnsupportedOperationException()
- }
+ if (element is KtDeclaration) {
+ val descriptor = resolveToDescriptor(element)
+ return object : BindingContext {
+ override fun <K : Any?, V : Any?> getKeys(p0: WritableSlice<K, V>?): Collection<K> {
+ throw UnsupportedOperationException()
+ }
- override fun analyzeFullyAndGetResult(elements: Collection<KtElement>): AnalysisResult {
+ override fun getType(p0: KtExpression): KotlinType? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <K : Any?, V : Any?> get(slice: ReadOnlySlice<K, V>?, key: K): V? {
+ if (key != element) {
+ throw UnsupportedOperationException()
+ }
+ return when {
+ slice == BindingContext.DECLARATION_TO_DESCRIPTOR -> descriptor as V
+ slice == BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER && (element as KtParameter).hasValOrVar() -> descriptor as V
+ else -> null
+ }
+ }
+
+ override fun getDiagnostics(): Diagnostics {
+ throw UnsupportedOperationException()
+ }
+
+ override fun addOwnDataTo(p0: BindingTrace, p1: Boolean) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <K : Any?, V : Any?> getSliceContents(p0: ReadOnlySlice<K, V>): ImmutableMap<K, V> {
+ throw UnsupportedOperationException()
+ }
+
+ }
+ }
throw UnsupportedOperationException()
}
diff --git a/core/src/main/kotlin/Analysis/CoreKotlinCacheService.kt b/core/src/main/kotlin/Analysis/CoreKotlinCacheService.kt
new file mode 100644
index 00000000..31b8ffc7
--- /dev/null
+++ b/core/src/main/kotlin/Analysis/CoreKotlinCacheService.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiFile
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.resolve.TargetPlatform
+import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache
+
+
+class CoreKotlinCacheService(private val resolutionFacade: DokkaResolutionFacade) : KotlinCacheService {
+ override fun getResolutionFacade(elements: List<KtElement>): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeByFile(file: PsiFile, platform: TargetPlatform): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeByModuleInfo(moduleInfo: ModuleInfo, platform: TargetPlatform): ResolutionFacade? {
+ return resolutionFacade
+ }
+
+ override fun getSuppressionCache(): KotlinSuppressCache {
+ throw UnsupportedOperationException()
+ }
+
+}
+
diff --git a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt
index 2f2f94b3..4f6a7c76 100644
--- a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt
+++ b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt
@@ -228,24 +228,26 @@ class CoreProjectFileIndex(private val project: Project, contentRoots: List<Cont
}
private val moduleSourceOrderEntry = object : ModuleSourceOrderEntry {
- override fun getFiles(p0: OrderRootType?): Array<out VirtualFile> {
+ override fun getFiles(p0: OrderRootType): Array<VirtualFile> {
throw UnsupportedOperationException()
}
- override fun getPresentableName(): String {
+ override fun getUrls(p0: OrderRootType): Array<String> {
throw UnsupportedOperationException()
}
- override fun getUrls(p0: OrderRootType?): Array<out String> {
+ override fun <R : Any?> accept(p0: RootPolicy<R>, p1: R?): R {
throw UnsupportedOperationException()
}
- override fun getOwnerModule(): Module = module
- override fun <R : Any?> accept(p0: RootPolicy<R>?, p1: R?): R {
+ override fun getPresentableName(): String {
throw UnsupportedOperationException()
}
+ override fun getOwnerModule(): Module = module
+
+
override fun isValid(): Boolean {
throw UnsupportedOperationException()
}
@@ -262,29 +264,29 @@ class CoreProjectFileIndex(private val project: Project, contentRoots: List<Cont
}
private val sdkOrderEntry = object : JdkOrderEntry {
- override fun getJdkName(): String? {
+ override fun getFiles(p0: OrderRootType): Array<VirtualFile> {
throw UnsupportedOperationException()
}
- override fun getJdk(): Sdk = sdk
-
- override fun getFiles(p0: OrderRootType?): Array<out VirtualFile> {
+ override fun getUrls(p0: OrderRootType): Array<String> {
throw UnsupportedOperationException()
}
- override fun getPresentableName(): String {
+ override fun <R : Any?> accept(p0: RootPolicy<R>, p1: R?): R {
throw UnsupportedOperationException()
}
- override fun getUrls(p0: OrderRootType?): Array<out String> {
+ override fun getJdkName(): String? {
throw UnsupportedOperationException()
}
- override fun getOwnerModule(): Module {
+ override fun getJdk(): Sdk = sdk
+
+ override fun getPresentableName(): String {
throw UnsupportedOperationException()
}
- override fun <R : Any?> accept(p0: RootPolicy<R>?, p1: R?): R {
+ override fun getOwnerModule(): Module {
throw UnsupportedOperationException()
}
@@ -358,7 +360,7 @@ class CoreProjectFileIndex(private val project: Project, contentRoots: List<Cont
override fun getRootModel(p0: Module): ModuleRootModel = this@MyModuleRootManager
})
- override fun <T : Any?> getModuleExtension(p0: Class<T>?): T {
+ override fun <T : Any?> getModuleExtension(p0: Class<T>): T {
throw UnsupportedOperationException()
}
diff --git a/core/src/main/kotlin/Analysis/JavaResolveExtension.kt b/core/src/main/kotlin/Analysis/JavaResolveExtension.kt
new file mode 100644
index 00000000..4dc6b366
--- /dev/null
+++ b/core/src/main/kotlin/Analysis/JavaResolveExtension.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+@file:JvmName("JavaResolutionUtils")
+
+package org.jetbrains.dokka
+
+import com.intellij.psi.*
+import org.jetbrains.kotlin.asJava.classes.KtLightClass
+import org.jetbrains.kotlin.asJava.unwrapped
+import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
+import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
+import org.jetbrains.kotlin.load.java.structure.*
+import org.jetbrains.kotlin.load.java.structure.impl.*
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
+import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
+
+// TODO: Remove that file
+
+@JvmOverloads
+fun PsiMethod.getJavaMethodDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? {
+ val method = originalElement as? PsiMethod ?: return null
+ if (method.containingClass == null || !Name.isValidIdentifier(method.name)) return null
+ val resolver = method.getJavaDescriptorResolver(resolutionFacade)
+ return when {
+ method.isConstructor -> resolver?.resolveConstructor(JavaConstructorImpl(method))
+ else -> resolver?.resolveMethod(JavaMethodImpl(method))
+ }
+}
+
+@JvmOverloads
+fun PsiClass.getJavaClassDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): ClassDescriptor? {
+ val psiClass = originalElement as? PsiClass ?: return null
+ return psiClass.getJavaDescriptorResolver(resolutionFacade)?.resolveClass(JavaClassImpl(psiClass))
+}
+
+@JvmOverloads
+fun PsiField.getJavaFieldDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): PropertyDescriptor? {
+ val field = originalElement as? PsiField ?: return null
+ return field.getJavaDescriptorResolver(resolutionFacade)?.resolveField(JavaFieldImpl(field))
+}
+
+@JvmOverloads
+fun PsiMember.getJavaMemberDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? {
+ return when (this) {
+ is PsiEnumConstant -> containingClass?.getJavaClassDescriptor(resolutionFacade)
+ is PsiClass -> getJavaClassDescriptor(resolutionFacade)
+ is PsiMethod -> getJavaMethodDescriptor(resolutionFacade)
+ is PsiField -> getJavaFieldDescriptor(resolutionFacade)
+ else -> null
+ }
+}
+
+@JvmOverloads
+fun PsiMember.getJavaOrKotlinMemberDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? {
+ val callable = unwrapped
+ return when (callable) {
+ is PsiMember -> getJavaMemberDescriptor(resolutionFacade)
+ is KtDeclaration -> {
+ val descriptor = resolutionFacade.resolveToDescriptor(callable)
+ if (descriptor is ClassDescriptor && this is PsiMethod) descriptor.unsubstitutedPrimaryConstructor else descriptor
+ }
+ else -> null
+ }
+}
+
+private fun PsiElement.getJavaDescriptorResolver(resolutionFacade: ResolutionFacade): JavaDescriptorResolver? {
+ return resolutionFacade.tryGetFrontendService(this, JavaDescriptorResolver::class.java)
+}
+
+private fun JavaDescriptorResolver.resolveMethod(method: JavaMethod): DeclarationDescriptor? {
+ return getContainingScope(method)
+ ?.getContributedDescriptors(nameFilter = { true }, kindFilter = DescriptorKindFilter.CALLABLES)
+ ?.filterIsInstance<DeclarationDescriptorWithSource>()
+ ?.findByJavaElement(method)
+}
+
+private fun JavaDescriptorResolver.resolveConstructor(constructor: JavaConstructor): ConstructorDescriptor? {
+ return resolveClass(constructor.containingClass)?.constructors?.findByJavaElement(constructor)
+}
+
+private fun JavaDescriptorResolver.resolveField(field: JavaField): PropertyDescriptor? {
+ return getContainingScope(field)?.getContributedVariables(field.name, NoLookupLocation.FROM_IDE)?.findByJavaElement(field)
+}
+
+private fun JavaDescriptorResolver.getContainingScope(member: JavaMember): MemberScope? {
+ val containingClass = resolveClass(member.containingClass)
+ return if (member.isStatic)
+ containingClass?.staticScope
+ else
+ containingClass?.defaultType?.memberScope
+}
+
+private fun <T : DeclarationDescriptorWithSource> Collection<T>.findByJavaElement(javaElement: JavaElement): T? {
+ return firstOrNull { member ->
+ val memberJavaElement = (member.original.source as? JavaSourceElement)?.javaElement
+ when {
+ memberJavaElement == javaElement ->
+ true
+ memberJavaElement is JavaElementImpl<*> && javaElement is JavaElementImpl<*> ->
+ memberJavaElement.psi.isEquivalentTo(javaElement.psi)
+ else ->
+ false
+ }
+ }
+}
+
+fun PsiElement.javaResolutionFacade() =
+ KotlinCacheService.getInstance(project).getResolutionFacadeByFile(this.originalElement.containingFile, JvmPlatform)!!
diff --git a/core/src/main/kotlin/DokkaBootstrapImpl.kt b/core/src/main/kotlin/DokkaBootstrapImpl.kt
index 126a0175..ccafcd12 100644
--- a/core/src/main/kotlin/DokkaBootstrapImpl.kt
+++ b/core/src/main/kotlin/DokkaBootstrapImpl.kt
@@ -2,7 +2,6 @@ package org.jetbrains.dokka
import org.jetbrains.dokka.DokkaConfiguration.PackageOptions
import ru.yole.jkid.deserialization.deserialize
-import java.io.File
import java.util.function.BiConsumer
@@ -44,34 +43,7 @@ class DokkaBootstrapImpl : DokkaBootstrap {
= configure(DokkaProxyLogger(logger), deserialize<DokkaConfigurationImpl>(serializedConfigurationJSON))
fun configure(logger: DokkaLogger, configuration: DokkaConfiguration) = with(configuration) {
- generator = DokkaGenerator(
- logger,
- classpath,
- sourceRoots,
- samples,
- includes,
- moduleName,
- DocumentationOptions(
- outputDir,
- format,
- includeNonPublic,
- includeRootPackage,
- reportUndocumented,
- skipEmptyPackages,
- skipDeprecated,
- jdkVersion,
- generateIndexPages,
- sourceLinks,
- impliedPlatforms,
- perPackageOptions,
- externalDocumentationLinks,
- noStdlibLink,
- languageVersion,
- apiVersion,
- cacheRoot,
- suppressedFiles.map { File(it) }
- )
- )
+ generator = DokkaGenerator(configuration, logger)
}
override fun generate() = generator.generate()
diff --git a/core/src/main/kotlin/Formats/AnalysisComponents.kt b/core/src/main/kotlin/Formats/AnalysisComponents.kt
new file mode 100644
index 00000000..d78d4a0c
--- /dev/null
+++ b/core/src/main/kotlin/Formats/AnalysisComponents.kt
@@ -0,0 +1,45 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Binder
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.KotlinAsJavaElementSignatureProvider
+import org.jetbrains.dokka.KotlinElementSignatureProvider
+import org.jetbrains.dokka.ElementSignatureProvider
+import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
+import org.jetbrains.dokka.Samples.SampleProcessingService
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.toType
+import kotlin.reflect.KClass
+
+
+interface DefaultAnalysisComponentServices {
+ val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder>
+ val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder>
+ val sampleProcessingService: KClass<out SampleProcessingService>
+ val elementSignatureProvider: KClass<out ElementSignatureProvider>
+}
+
+interface DefaultAnalysisComponent : FormatDescriptorAnalysisComponent, DefaultAnalysisComponentServices {
+ override fun configureAnalysis(binder: Binder): Unit = with(binder) {
+ bind<ElementSignatureProvider>() toType elementSignatureProvider
+ bind<PackageDocumentationBuilder>() toType packageDocumentationBuilderClass
+ bind<JavaDocumentationBuilder>() toType javaDocumentationBuilderClass
+ bind<SampleProcessingService>() toType sampleProcessingService
+ }
+}
+
+
+object KotlinAsJava : DefaultAnalysisComponentServices {
+ override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
+ override val sampleProcessingService = DefaultSampleProcessingService::class
+ override val elementSignatureProvider = KotlinAsJavaElementSignatureProvider::class
+}
+
+
+object KotlinAsKotlin : DefaultAnalysisComponentServices {
+ override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
+ override val sampleProcessingService = DefaultSampleProcessingService::class
+ override val elementSignatureProvider = KotlinElementSignatureProvider::class
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt
index da0156a7..4bac8aa0 100644
--- a/core/src/main/kotlin/Formats/FormatDescriptor.kt
+++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt
@@ -1,17 +1,43 @@
package org.jetbrains.dokka.Formats
+import com.google.inject.Binder
import org.jetbrains.dokka.*
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.Samples.SampleProcessingService
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.lazyBind
+import org.jetbrains.dokka.Utilities.toOptional
+import org.jetbrains.dokka.Utilities.toType
import kotlin.reflect.KClass
-interface FormatDescriptor {
- val formatServiceClass: KClass<out FormatService>?
- val outlineServiceClass: KClass<out OutlineFormatService>?
- val generatorServiceClass: KClass<out Generator>
- val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder>
- val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder>
- val sampleProcessingService: KClass<out SampleProcessingService>
- val packageListServiceClass: KClass<out PackageListService>?
- val descriptorSignatureProvider: KClass<out DescriptorSignatureProvider>
+
+interface FormatDescriptorAnalysisComponent {
+ fun configureAnalysis(binder: Binder)
+}
+
+interface FormatDescriptorOutputComponent {
+ fun configureOutput(binder: Binder)
}
+
+interface FormatDescriptor : FormatDescriptorAnalysisComponent, FormatDescriptorOutputComponent
+
+
+abstract class FileGeneratorBasedFormatDescriptor : FormatDescriptor {
+
+ override fun configureOutput(binder: Binder): Unit = with(binder) {
+ bind<Generator>() toType NodeLocationAwareGenerator::class
+ bind<NodeLocationAwareGenerator>() toType generatorServiceClass
+ bind(generatorServiceClass.java) // https://github.com/google/guice/issues/847
+
+ bind<LanguageService>() toType languageServiceClass
+
+ lazyBind<OutlineFormatService>() toOptional (outlineServiceClass)
+ lazyBind<FormatService>() toOptional formatServiceClass
+ lazyBind<PackageListService>() toOptional packageListServiceClass
+ }
+
+ abstract val formatServiceClass: KClass<out FormatService>?
+ abstract val outlineServiceClass: KClass<out OutlineFormatService>?
+ abstract val generatorServiceClass: KClass<out FileGenerator>
+ abstract val packageListServiceClass: KClass<out PackageListService>?
+
+ open val languageServiceClass: KClass<out LanguageService> = KotlinLanguageService::class
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/GFMFormatService.kt b/core/src/main/kotlin/Formats/GFMFormatService.kt
index f741561c..036ec856 100644
--- a/core/src/main/kotlin/Formats/GFMFormatService.kt
+++ b/core/src/main/kotlin/Formats/GFMFormatService.kt
@@ -4,14 +4,14 @@ import com.google.inject.Inject
import com.google.inject.name.Named
import org.jetbrains.dokka.Utilities.impliedPlatformsName
-open class GFMOutputBuilder(to: StringBuilder,
- location: Location,
- locationService: LocationService,
- languageService: LanguageService,
- extension: String,
- impliedPlatforms: List<String>)
- : MarkdownOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
-{
+open class GFMOutputBuilder(
+ to: StringBuilder,
+ location: Location,
+ generator: NodeLocationAwareGenerator,
+ languageService: LanguageService,
+ extension: String,
+ impliedPlatforms: List<String>
+) : MarkdownOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) {
override fun appendTable(vararg columns: String, body: () -> Unit) {
to.appendln(columns.joinToString(" | ", "| ", " |"))
to.appendln("|" + "---|".repeat(columns.size))
@@ -21,8 +21,7 @@ open class GFMOutputBuilder(to: StringBuilder,
override fun appendUnorderedList(body: () -> Unit) {
if (inTableCell) {
wrapInTag("ul", body)
- }
- else {
+ } else {
super.appendUnorderedList(body)
}
}
@@ -30,8 +29,7 @@ open class GFMOutputBuilder(to: StringBuilder,
override fun appendOrderedList(body: () -> Unit) {
if (inTableCell) {
wrapInTag("ol", body)
- }
- else {
+ } else {
super.appendOrderedList(body)
}
}
@@ -39,23 +37,25 @@ open class GFMOutputBuilder(to: StringBuilder,
override fun appendListItem(body: () -> Unit) {
if (inTableCell) {
wrapInTag("li", body)
- }
- else {
+ } else {
super.appendListItem(body)
}
}
}
-open class GFMFormatService(locationService: LocationService,
- signatureGenerator: LanguageService,
- linkExtension: String,
- impliedPlatforms: List<String>)
-: MarkdownFormatService(locationService, signatureGenerator, linkExtension, impliedPlatforms) {
+open class GFMFormatService(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ linkExtension: String,
+ impliedPlatforms: List<String>
+) : MarkdownFormatService(generator, signatureGenerator, linkExtension, impliedPlatforms) {
- @Inject constructor(locationService: LocationService,
- signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>) : this(locationService, signatureGenerator, "md", impliedPlatforms)
+ @Inject constructor(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>
+ ) : this(generator, signatureGenerator, "md", impliedPlatforms)
override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder =
- GFMOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ GFMOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
}
diff --git a/core/src/main/kotlin/Formats/HtmlFormatService.kt b/core/src/main/kotlin/Formats/HtmlFormatService.kt
index 5e05f51a..0ad946be 100644
--- a/core/src/main/kotlin/Formats/HtmlFormatService.kt
+++ b/core/src/main/kotlin/Formats/HtmlFormatService.kt
@@ -4,17 +4,15 @@ import com.google.inject.Inject
import com.google.inject.name.Named
import org.jetbrains.dokka.Utilities.impliedPlatformsName
import java.io.File
-import java.nio.file.Path
-import java.nio.file.Paths
open class HtmlOutputBuilder(to: StringBuilder,
location: Location,
- locationService: LocationService,
+ generator: NodeLocationAwareGenerator,
languageService: LanguageService,
extension: String,
impliedPlatforms: List<String>,
val templateService: HtmlTemplateService)
- : StructuredOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ : StructuredOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
{
override fun appendText(text: String) {
to.append(text.htmlEscape())
@@ -81,7 +79,7 @@ open class HtmlOutputBuilder(to: StringBuilder,
}
override fun appendNodes(nodes: Iterable<DocumentationNode>) {
- templateService.appendHeader(to, getPageTitle(nodes), locationService.calcPathToRoot(location))
+ templateService.appendHeader(to, getPageTitle(nodes), generator.relativePathToRoot(location))
super.appendNodes(nodes)
templateService.appendFooter(to)
}
@@ -95,21 +93,21 @@ open class HtmlOutputBuilder(to: StringBuilder,
}
}
-open class HtmlFormatService @Inject constructor(@Named("folders") locationService: LocationService,
+open class HtmlFormatService @Inject constructor(generator: NodeLocationAwareGenerator,
signatureGenerator: LanguageService,
val templateService: HtmlTemplateService,
@Named(impliedPlatformsName) val impliedPlatforms: List<String>)
-: StructuredFormatService(locationService, signatureGenerator, "html"), OutlineFormatService {
+: StructuredFormatService(generator, signatureGenerator, "html"), OutlineFormatService {
override fun enumerateSupportFiles(callback: (String, String) -> Unit) {
callback("/dokka/styles/style.css", "style.css")
}
override fun createOutputBuilder(to: StringBuilder, location: Location) =
- HtmlOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms, templateService)
+ HtmlOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms, templateService)
override fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
- templateService.appendHeader(to, "Module Contents", locationService.calcPathToRoot(location))
+ templateService.appendHeader(to, "Module Contents", generator.relativePathToRoot(location))
super.appendOutline(location, to, nodes)
templateService.appendFooter(to)
}
@@ -123,7 +121,7 @@ open class HtmlFormatService @Inject constructor(@Named("folders") locationServi
link.append(languageService.render(node, LanguageService.RenderMode.FULL))
val tempBuilder = StringBuilder()
createOutputBuilder(tempBuilder, location).appendContent(link)
- to.appendln("<a href=\"${location.path}\">${tempBuilder.toString()}</a><br/>")
+ to.appendln("<a href=\"${location.path}\">$tempBuilder</a><br/>")
}
override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
@@ -133,11 +131,6 @@ open class HtmlFormatService @Inject constructor(@Named("folders") locationServi
}
}
-private fun LocationService.calcPathToRoot(location: Location): Path {
- val path = Paths.get(location.path)
- return path.parent?.relativize(Paths.get(root.path + '/')) ?: path
-}
-
fun getPageTitle(nodes: Iterable<DocumentationNode>): String? {
val breakdownByLocation = nodes.groupBy { node -> formatPageTitle(node) }
return breakdownByLocation.keys.singleOrNull()
diff --git a/core/src/main/kotlin/Formats/HtmlTemplateService.kt b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
index 010bc702..a65a7b18 100644
--- a/core/src/main/kotlin/Formats/HtmlTemplateService.kt
+++ b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
@@ -1,9 +1,9 @@
package org.jetbrains.dokka
-import java.nio.file.Path
+import java.io.File
interface HtmlTemplateService {
- fun appendHeader(to: StringBuilder, title: String?, basePath: Path)
+ fun appendHeader(to: StringBuilder, title: String?, basePath: File)
fun appendFooter(to: StringBuilder)
companion object {
@@ -16,7 +16,7 @@ interface HtmlTemplateService {
to.appendln("</BODY>")
to.appendln("</HTML>")
}
- override fun appendHeader(to: StringBuilder, title: String?, basePath: Path) {
+ override fun appendHeader(to: StringBuilder, title: String?, basePath: File) {
to.appendln("<HTML>")
to.appendln("<HEAD>")
to.appendln("<meta charset=\"UTF-8\">")
@@ -24,7 +24,7 @@ interface HtmlTemplateService {
to.appendln("<title>$title</title>")
}
if (css != null) {
- val cssPath = basePath.resolve(css)
+ val cssPath = basePath.resolve(css).toUnixString()
to.appendln("<link rel=\"stylesheet\" href=\"$cssPath\">")
}
to.appendln("</HEAD>")
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
new file mode 100644
index 00000000..09bb2602
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
@@ -0,0 +1,117 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
+import org.jetbrains.kotlin.types.KotlinType
+
+class JavaLayoutHtmlPackageListService: PackageListService {
+
+ private fun StringBuilder.appendParam(name: String, value: String) {
+ append(DOKKA_PARAM_PREFIX)
+ append(name)
+ append(":")
+ appendln(value)
+ }
+
+ override fun formatPackageList(module: DocumentationModule): String {
+ val packages = module.members(NodeKind.Package).map { it.name }
+
+ return buildString {
+ appendParam("format", "java-layout-html")
+ appendParam("mode", "kotlin")
+ for (p in packages) {
+ appendln(p)
+ }
+ }
+ }
+
+}
+
+class JavaLayoutHtmlInboundLinkResolutionService(private val paramMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+ private fun getContainerPath(symbol: DeclarationDescriptor): String? {
+ return when (symbol) {
+ is PackageFragmentDescriptor -> symbol.fqName.asString().replace('.', '/') + "/"
+ is ClassifierDescriptor -> getContainerPath(symbol.findPackage()) + symbol.nameWithOuter() + ".html"
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.findPackage(): PackageFragmentDescriptor =
+ generateSequence(this) { it.containingDeclaration }.filterIsInstance<PackageFragmentDescriptor>().first()
+
+ private fun ClassifierDescriptor.nameWithOuter(): String =
+ generateSequence(this) { it.containingDeclaration as? ClassifierDescriptor }
+ .toList().asReversed().joinToString(".") { it.name.asString() }
+
+ private fun getPagePath(symbol: DeclarationDescriptor): String? {
+ return when (symbol) {
+ is PackageFragmentDescriptor -> getContainerPath(symbol) + "package-summary.html"
+ is EnumEntrySyntheticClassDescriptor -> getContainerPath(symbol.containingDeclaration) + "#" + symbol.signatureForAnchorUrlEncoded()
+ is ClassifierDescriptor -> getContainerPath(symbol) + "#"
+ is FunctionDescriptor, is PropertyDescriptor -> getContainerPath(symbol.containingDeclaration!!) + "#" + symbol.signatureForAnchorUrlEncoded()
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.signatureForAnchor(): String? {
+
+ fun ReceiverParameterDescriptor.extractReceiverName(): String {
+ var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!!
+ if (receiverClass.isCompanionObject()) {
+ receiverClass = receiverClass.containingDeclaration!!
+ } else if (receiverClass is TypeParameterDescriptor) {
+ val upperBoundClass = receiverClass.upperBounds.singleOrNull()?.constructor?.declarationDescriptor
+ if (upperBoundClass != null) {
+ receiverClass = upperBoundClass
+ }
+ }
+
+ return receiverClass.name.asString()
+ }
+
+ fun KotlinType.qualifiedNameForSignature(): String {
+ val desc = constructor.declarationDescriptor
+ return desc?.fqNameUnsafe?.asString() ?: "<ERROR TYPE NAME>"
+ }
+
+ fun StringBuilder.appendReceiverAndCompanion(desc: CallableDescriptor) {
+ if (desc.containingDeclaration.isCompanionObject()) {
+ append("Companion.")
+ }
+ desc.extensionReceiverParameter?.let {
+ append("(")
+ append(it.extractReceiverName())
+ append(").")
+ }
+ }
+
+ return when(this) {
+ is EnumEntrySyntheticClassDescriptor -> buildString {
+ append("ENUM_VALUE:")
+ append(name.asString())
+ }
+ is FunctionDescriptor -> buildString {
+ appendReceiverAndCompanion(this@signatureForAnchor)
+ append(name.asString())
+ valueParameters.joinTo(this, prefix = "(", postfix = ")") {
+ it.type.qualifiedNameForSignature()
+ }
+ }
+ is PropertyDescriptor -> buildString {
+ appendReceiverAndCompanion(this@signatureForAnchor)
+ append(name.asString())
+ append(":")
+ append(returnType?.qualifiedNameForSignature())
+ }
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.signatureForAnchorUrlEncoded(): String? = signatureForAnchor()?.urlEncoded()
+
+ override fun getPath(symbol: DeclarationDescriptor) = getPagePath(symbol)
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
new file mode 100644
index 00000000..885cdf6c
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
@@ -0,0 +1,91 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Binder
+import com.google.inject.Inject
+import kotlinx.html.li
+import kotlinx.html.stream.appendHTML
+import kotlinx.html.ul
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.toType
+import java.io.File
+
+
+class JavaLayoutHtmlFormatDescriptor : FormatDescriptor, DefaultAnalysisComponent {
+ override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
+ override val sampleProcessingService = DefaultSampleProcessingService::class
+ override val elementSignatureProvider = KotlinElementSignatureProvider::class
+
+ override fun configureOutput(binder: Binder): Unit = with(binder) {
+ bind<Generator>() toType generatorServiceClass
+ }
+
+ val formatServiceClass = JavaLayoutHtmlFormatService::class
+ val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
+}
+
+
+class JavaLayoutHtmlFormatService : FormatService {
+ override val extension: String
+ get() = TODO("not implemented")
+
+
+ override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder {
+ TODO("not implemented")
+ }
+}
+
+class JavaLayoutHtmlFormatOutputBuilder : FormattedOutputBuilder {
+ override fun appendNodes(nodes: Iterable<DocumentationNode>) {
+
+ }
+}
+
+
+class JavaLayoutHtmlFormatNavListBuilder @Inject constructor() : OutlineFormatService {
+ override fun getOutlineFileName(location: Location): File {
+ TODO()
+ }
+
+ override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) {
+ with(to.appendHTML()) {
+ //a(href = )
+ li {
+ when {
+ node.kind == NodeKind.Package -> appendOutline(location, to, node.members)
+ }
+ }
+ }
+ }
+
+ override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
+ with(to.appendHTML()) {
+ ul { body() }
+ }
+ }
+
+}
+
+class JavaLayoutHtmlFormatGenerator @Inject constructor(
+ private val outlineFormatService: OutlineFormatService
+) : Generator {
+ override fun buildPages(nodes: Iterable<DocumentationNode>) {
+
+ }
+
+ override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
+ for (node in nodes) {
+ if (node.kind == NodeKind.Module) {
+ //outlineFormatService.formatOutline()
+ }
+ }
+ }
+
+ override fun buildSupportFiles() {}
+
+ override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
+
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JekyllFormatService.kt b/core/src/main/kotlin/Formats/JekyllFormatService.kt
index 6b97bbe0..a948dfa9 100644
--- a/core/src/main/kotlin/Formats/JekyllFormatService.kt
+++ b/core/src/main/kotlin/Formats/JekyllFormatService.kt
@@ -6,12 +6,11 @@ import org.jetbrains.dokka.Utilities.impliedPlatformsName
open class JekyllOutputBuilder(to: StringBuilder,
location: Location,
- locationService: LocationService,
+ generator: NodeLocationAwareGenerator,
languageService: LanguageService,
extension: String,
impliedPlatforms: List<String>)
- : MarkdownOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
-{
+ : MarkdownOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) {
override fun appendNodes(nodes: Iterable<DocumentationNode>) {
to.appendln("---")
appendFrontMatter(nodes, to)
@@ -26,17 +25,20 @@ open class JekyllOutputBuilder(to: StringBuilder,
}
-open class JekyllFormatService(locationService: LocationService,
- signatureGenerator: LanguageService,
- linkExtension: String,
- impliedPlatforms: List<String>)
-: MarkdownFormatService(locationService, signatureGenerator, linkExtension, impliedPlatforms) {
+open class JekyllFormatService(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ linkExtension: String,
+ impliedPlatforms: List<String>
+) : MarkdownFormatService(generator, signatureGenerator, linkExtension, impliedPlatforms) {
- @Inject constructor(locationService: LocationService,
- signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>): this(locationService, signatureGenerator, "html", impliedPlatforms)
+ @Inject constructor(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>
+ ) : this(generator, signatureGenerator, "html", impliedPlatforms)
override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder =
- JekyllOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ JekyllOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
}
diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
index 08349980..a98002d4 100644
--- a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
+++ b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
@@ -6,14 +6,14 @@ import org.jetbrains.dokka.Utilities.impliedPlatformsName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
-open class KotlinWebsiteOutputBuilder(to: StringBuilder,
- location: Location,
- locationService: LocationService,
- languageService: LanguageService,
- extension: String,
- impliedPlatforms: List<String>)
- : JekyllOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
-{
+open class KotlinWebsiteOutputBuilder(
+ to: StringBuilder,
+ location: Location,
+ generator: NodeLocationAwareGenerator,
+ languageService: LanguageService,
+ extension: String,
+ impliedPlatforms: List<String>
+) : JekyllOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) {
private var needHardLineBreaks = false
private var insideDiv = 0
@@ -70,8 +70,7 @@ open class KotlinWebsiteOutputBuilder(to: StringBuilder,
override fun appendHeader(level: Int, body: () -> Unit) {
if (insideDiv > 0) {
wrapInTag("p", body, newlineAfterClose = true)
- }
- else {
+ } else {
super.appendHeader(level, body)
}
}
@@ -79,8 +78,7 @@ open class KotlinWebsiteOutputBuilder(to: StringBuilder,
override fun appendLine() {
if (insideDiv > 0) {
to.appendln("<br/>")
- }
- else {
+ } else {
super.appendLine()
}
}
@@ -135,13 +133,14 @@ open class KotlinWebsiteOutputBuilder(to: StringBuilder,
to.append("<br/>")
}
+
override fun appendIndentedSoftLineBreak() {
if (needHardLineBreaks) {
to.append("<br/>&nbsp;&nbsp;&nbsp;&nbsp;")
}
}
- private fun identifierClassName(kind: IdentifierKind) = when(kind) {
+ private fun identifierClassName(kind: IdentifierKind) = when (kind) {
IdentifierKind.ParameterName -> "parameterName"
IdentifierKind.SummarizedTypeName -> "summarizedTypeName"
else -> "identifier"
@@ -172,28 +171,29 @@ open class KotlinWebsiteOutputBuilder(to: StringBuilder,
}
}
-class KotlinWebsiteFormatService @Inject constructor(locationService: LocationService,
- signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>,
- logger: DokkaLogger)
- : JekyllFormatService(locationService, signatureGenerator, "html", impliedPlatforms)
-{
+class KotlinWebsiteFormatService @Inject constructor(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>,
+ logger: DokkaLogger
+) : JekyllFormatService(generator, signatureGenerator, "html", impliedPlatforms) {
init {
logger.warn("Format kotlin-website deprecated and will be removed in next release")
}
override fun createOutputBuilder(to: StringBuilder, location: Location) =
- KotlinWebsiteOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ KotlinWebsiteOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
}
-class KotlinWebsiteRunnableSamplesOutputBuilder(to: StringBuilder,
- location: Location,
- locationService: LocationService,
- languageService: LanguageService,
- extension: String,
- impliedPlatforms: List<String>)
- : KotlinWebsiteOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms) {
+class KotlinWebsiteRunnableSamplesOutputBuilder(
+ to: StringBuilder,
+ location: Location,
+ generator: NodeLocationAwareGenerator,
+ languageService: LanguageService,
+ extension: String,
+ impliedPlatforms: List<String>
+) : KotlinWebsiteOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) {
override fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) {
div(to, "sample", markdown = true) {
@@ -207,17 +207,18 @@ class KotlinWebsiteRunnableSamplesOutputBuilder(to: StringBuilder,
}
}
-class KotlinWebsiteRunnableSamplesFormatService @Inject constructor(locationService: LocationService,
- signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>,
- logger: DokkaLogger)
- : JekyllFormatService(locationService, signatureGenerator, "html", impliedPlatforms) {
+class KotlinWebsiteRunnableSamplesFormatService @Inject constructor(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>,
+ logger: DokkaLogger
+) : JekyllFormatService(generator, signatureGenerator, "html", impliedPlatforms) {
init {
logger.warn("Format kotlin-website-samples deprecated and will be removed in next release")
}
override fun createOutputBuilder(to: StringBuilder, location: Location) =
- KotlinWebsiteRunnableSamplesOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ KotlinWebsiteRunnableSamplesOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
}
diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt
index 378401f3..16bec5a6 100644
--- a/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt
+++ b/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt
@@ -4,23 +4,25 @@ import com.google.inject.Inject
import com.google.inject.name.Named
import org.jetbrains.dokka.Utilities.impliedPlatformsName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
-import java.nio.file.Path
+import java.io.File
-private object EmptyHtmlTemplateService : HtmlTemplateService {
+object EmptyHtmlTemplateService : HtmlTemplateService {
override fun appendFooter(to: StringBuilder) {}
- override fun appendHeader(to: StringBuilder, title: String?, basePath: Path) {}
+ override fun appendHeader(to: StringBuilder, title: String?, basePath: File) {}
}
-open class KotlinWebsiteHtmlOutputBuilder(to: StringBuilder,
- location: Location,
- locationService: LocationService,
- languageService: LanguageService,
- extension: String,
- impliedPlatforms: List<String>)
- : HtmlOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms, EmptyHtmlTemplateService) {
+open class KotlinWebsiteHtmlOutputBuilder(
+ to: StringBuilder,
+ location: Location,
+ generator: NodeLocationAwareGenerator,
+ languageService: LanguageService,
+ extension: String,
+ impliedPlatforms: List<String>,
+ templateService: HtmlTemplateService
+) : HtmlOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms, templateService) {
private var needHardLineBreaks = false
private var insideDiv = 0
@@ -167,16 +169,25 @@ open class KotlinWebsiteHtmlOutputBuilder(to: StringBuilder,
appendContent(section)
}
}
+
+ override fun appendAsBlockWithPlatforms(platforms: Set<String>, block: () -> Unit) {
+ if (platforms.isNotEmpty())
+ wrap("<div${calculateDataAttributes(platforms)}>", "</div>", block)
+ else
+ block()
+ }
}
-class KotlinWebsiteHtmlFormatService @Inject constructor(locationService: LocationService,
- signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>)
- : HtmlFormatService(locationService, signatureGenerator, EmptyHtmlTemplateService, impliedPlatforms) {
+class KotlinWebsiteHtmlFormatService @Inject constructor(
+ generator: NodeLocationAwareGenerator,
+ signatureGenerator: LanguageService,
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>,
+ templateService: HtmlTemplateService
+) : HtmlFormatService(generator, signatureGenerator, templateService, impliedPlatforms) {
override fun enumerateSupportFiles(callback: (String, String) -> Unit) {}
override fun createOutputBuilder(to: StringBuilder, location: Location) =
- KotlinWebsiteHtmlOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ KotlinWebsiteHtmlOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms, templateService)
}
diff --git a/core/src/main/kotlin/Formats/MarkdownFormatService.kt b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
index a7c18a28..4265394f 100644
--- a/core/src/main/kotlin/Formats/MarkdownFormatService.kt
+++ b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
@@ -21,11 +21,11 @@ private val TWO_LINE_BREAKS = System.lineSeparator() + System.lineSeparator()
open class MarkdownOutputBuilder(to: StringBuilder,
location: Location,
- locationService: LocationService,
+ generator: NodeLocationAwareGenerator,
languageService: LanguageService,
extension: String,
impliedPlatforms: List<String>)
- : StructuredOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ : StructuredOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
{
private val listStack = ArrayDeque<ListState>()
protected var inTableCell = false
@@ -225,15 +225,15 @@ open class MarkdownOutputBuilder(to: StringBuilder,
}
}
-open class MarkdownFormatService(locationService: LocationService,
+open class MarkdownFormatService(generator: NodeLocationAwareGenerator,
signatureGenerator: LanguageService,
linkExtension: String,
val impliedPlatforms: List<String>)
-: StructuredFormatService(locationService, signatureGenerator, "md", linkExtension) {
- @Inject constructor(locationService: LocationService,
+: StructuredFormatService(generator, signatureGenerator, "md", linkExtension) {
+ @Inject constructor(generator: NodeLocationAwareGenerator,
signatureGenerator: LanguageService,
- @Named(impliedPlatformsName) impliedPlatforms: List<String>): this(locationService, signatureGenerator, "md", impliedPlatforms)
+ @Named(impliedPlatformsName) impliedPlatforms: List<String>): this(generator, signatureGenerator, "md", impliedPlatforms)
override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder =
- MarkdownOutputBuilder(to, location, locationService, languageService, extension, impliedPlatforms)
+ MarkdownOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms)
}
diff --git a/core/src/main/kotlin/Formats/OutlineService.kt b/core/src/main/kotlin/Formats/OutlineService.kt
index 3c31ba57..958e93af 100644
--- a/core/src/main/kotlin/Formats/OutlineService.kt
+++ b/core/src/main/kotlin/Formats/OutlineService.kt
@@ -16,7 +16,7 @@ interface OutlineFormatService {
for (node in nodes) {
appendOutlineHeader(location, node, to)
if (node.members.any()) {
- val sortedMembers = node.members.sortedBy { it.name }
+ val sortedMembers = node.members.sortedBy { it.name.toLowerCase() }
appendOutlineLevel(to) {
appendOutline(location, to, sortedMembers)
}
diff --git a/core/src/main/kotlin/Formats/PackageListService.kt b/core/src/main/kotlin/Formats/PackageListService.kt
index e7f4e952..7b68098e 100644
--- a/core/src/main/kotlin/Formats/PackageListService.kt
+++ b/core/src/main/kotlin/Formats/PackageListService.kt
@@ -7,10 +7,10 @@ interface PackageListService {
fun formatPackageList(module: DocumentationModule): String
}
-class DefaultPackageListService @Inject constructor(locationService: FileLocationService,
- val formatService: FormatService) : PackageListService {
-
- val locationService: FileLocationService = locationService.withExtension(formatService.linkExtension)
+class DefaultPackageListService @Inject constructor(
+ val generator: NodeLocationAwareGenerator,
+ val formatService: FormatService
+) : PackageListService {
override fun formatPackageList(module: DocumentationModule): String {
val packages = mutableSetOf<String>()
@@ -26,7 +26,7 @@ class DefaultPackageListService @Inject constructor(locationService: FileLocatio
}
NodeKind.Signature -> {
if (relocated)
- nonStandardLocations[node.name] = locationService.relativePathToLocation(module, node.owner!!)
+ nonStandardLocations[node.name] = generator.relativePathToLocation(module, node.owner!!)
}
NodeKind.ExternalClass -> {
node.members.forEach { visit(it, relocated = true) }
diff --git a/core/src/main/kotlin/Formats/StandardFormats.kt b/core/src/main/kotlin/Formats/StandardFormats.kt
index f3d638c6..dd67ac97 100644
--- a/core/src/main/kotlin/Formats/StandardFormats.kt
+++ b/core/src/main/kotlin/Formats/StandardFormats.kt
@@ -1,41 +1,36 @@
package org.jetbrains.dokka.Formats
+import com.google.inject.Binder
import org.jetbrains.dokka.*
-import org.jetbrains.dokka.Kotlin.KotlinAsJavaDescriptorSignatureProvider
-import org.jetbrains.dokka.Kotlin.KotlinDescriptorSignatureProvider
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
import org.jetbrains.dokka.Samples.KotlinWebsiteSampleProcessingService
-import org.jetbrains.dokka.Samples.SampleProcessingService
+import org.jetbrains.dokka.Utilities.bind
import kotlin.reflect.KClass
-abstract class KotlinFormatDescriptorBase : FormatDescriptor {
- override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
- override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
-
+abstract class KotlinFormatDescriptorBase
+ : FileGeneratorBasedFormatDescriptor(),
+ DefaultAnalysisComponent,
+ DefaultAnalysisComponentServices by KotlinAsKotlin {
override val generatorServiceClass = FileGenerator::class
override val outlineServiceClass: KClass<out OutlineFormatService>? = null
- override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
override val packageListServiceClass: KClass<out PackageListService>? = DefaultPackageListService::class
- override val descriptorSignatureProvider = KotlinDescriptorSignatureProvider::class
-}
-
-class HtmlFormatDescriptor : KotlinFormatDescriptorBase() {
- override val formatServiceClass = HtmlFormatService::class
- override val outlineServiceClass = HtmlFormatService::class
}
-class HtmlAsJavaFormatDescriptor : FormatDescriptor {
+abstract class HtmlFormatDescriptorBase : FileGeneratorBasedFormatDescriptor(), DefaultAnalysisComponent {
override val formatServiceClass = HtmlFormatService::class
override val outlineServiceClass = HtmlFormatService::class
override val generatorServiceClass = FileGenerator::class
- override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
- override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
- override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
- override val packageListServiceClass: KClass<out PackageListService>? = DefaultPackageListService::class
- override val descriptorSignatureProvider = KotlinAsJavaDescriptorSignatureProvider::class
+ override val packageListServiceClass = DefaultPackageListService::class
+
+ override fun configureOutput(binder: Binder): Unit = with(binder) {
+ super.configureOutput(binder)
+ bind<HtmlTemplateService>().toProvider { HtmlTemplateService.default("style.css") }
+ }
}
+class HtmlFormatDescriptor : HtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin
+
+class HtmlAsJavaFormatDescriptor : HtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsJava
+
class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() {
override val formatServiceClass = KotlinWebsiteFormatService::class
override val outlineServiceClass = YamlOutlineService::class
@@ -51,6 +46,11 @@ class KotlinWebsiteHtmlFormatDescriptor : KotlinFormatDescriptorBase() {
override val formatServiceClass = KotlinWebsiteHtmlFormatService::class
override val sampleProcessingService = KotlinWebsiteSampleProcessingService::class
override val outlineServiceClass = YamlOutlineService::class
+
+ override fun configureOutput(binder: Binder) = with(binder) {
+ super.configureOutput(binder)
+ bind<HtmlTemplateService>().toInstance(EmptyHtmlTemplateService)
+ }
}
class JekyllFormatDescriptor : KotlinFormatDescriptorBase() {
diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt
index 5167a102..53172563 100644
--- a/core/src/main/kotlin/Formats/StructuredFormatService.kt
+++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt
@@ -7,11 +7,13 @@ data class FormatLink(val text: String, val href: String)
abstract class StructuredOutputBuilder(val to: StringBuilder,
val location: Location,
- val locationService: LocationService,
+ val generator: NodeLocationAwareGenerator,
val languageService: LanguageService,
val extension: String,
val impliedPlatforms: List<String>) : FormattedOutputBuilder {
+ protected fun DocumentationNode.location() = generator.location(this)
+
protected fun wrap(prefix: String, suffix: String, body: () -> Unit) {
to.append(prefix)
body()
@@ -81,6 +83,10 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
+ open fun appendAsBlockWithPlatforms(platforms: Set<String>, block: () -> Unit) {
+ block()
+ }
+
open fun appendSymbol(text: String) {
appendText(text)
}
@@ -190,30 +196,28 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
- open fun link(from: DocumentationNode,
- to: DocumentationNode,
- name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink = link(from, to, extension, name)
+ open fun link(
+ from: DocumentationNode,
+ to: DocumentationNode,
+ name: (DocumentationNode) -> String = DocumentationNode::name
+ ): FormatLink = link(from, to, extension, name)
- open fun link(from: DocumentationNode,
- to: DocumentationNode,
- extension: String,
- name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink {
- if (to.owner?.kind == NodeKind.GroupNode)
- return link(from, to.owner!!, extension, name)
+ open fun link(
+ from: DocumentationNode,
+ to: DocumentationNode,
+ extension: String,
+ name: (DocumentationNode) -> String = DocumentationNode::name
+ ): FormatLink =
+ FormatLink(name(to), from.location().relativePathTo(to.location()))
- if (from.owner?.kind == NodeKind.GroupNode)
- return link(from.owner!!, to, extension, name)
-
- return FormatLink(name(to), locationService.relativePathToLocation(from, to))
- }
fun locationHref(from: Location, to: DocumentationNode): String {
val topLevelPage = to.references(RefKind.TopLevelPage).singleOrNull()?.to
if (topLevelPage != null) {
val signature = to.detailOrNull(NodeKind.Signature)
- return from.relativePathTo(locationService.location(topLevelPage), signature?.name ?: to.name)
+ return from.relativePathTo(topLevelPage.location(), signature?.name ?: to.name)
}
- return from.relativePathTo(locationService.location(to))
+ return from.relativePathTo(to.location())
}
private fun DocumentationNode.isModuleOrPackage(): Boolean =
@@ -296,7 +300,12 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
private fun appendDocumentation(overloads: Iterable<DocumentationNode>, isSingleNode: Boolean) {
- val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
+ val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node ->
+ when (node.kind) {
+ NodeKind.GroupNode -> node.origins.first().content
+ else -> node.content
+ }
+ }
if (breakdownBySummary.size == 1) {
formatOverloadGroup(breakdownBySummary.values.single(), isSingleNode)
@@ -314,29 +323,90 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
private fun formatOverloadGroup(items: List<DocumentationNode>, isSingleNode: Boolean = false) {
for ((index, item) in items.withIndex()) {
if (index > 0) appendLine()
- val rendered = languageService.render(item)
- item.detailOrNull(NodeKind.Signature)?.let {
- if (item.kind !in NodeKind.classLike || !isSingleNode)
- appendAnchor(it.name)
- }
- appendAsSignature(rendered) {
- appendCode { appendContent(rendered) }
- item.appendSourceLink()
+
+ if (item.kind == NodeKind.GroupNode) {
+ renderGroupNode(item, isSingleNode)
+ } else {
+ renderSimpleNode(item, isSingleNode)
}
- item.appendOverrides()
- item.appendDeprecation()
- item.appendPlatforms()
+
}
// All items have exactly the same documentation, so we can use any item to render it
val item = items.first()
+ // TODO: remove this block cause there is no one node with OverloadGroupNote detail
item.details(NodeKind.OverloadGroupNote).forEach {
appendContent(it.content)
}
- appendContent(item.content.summary)
+ if (item.kind == NodeKind.GroupNode) {
+ for (origin in item.origins) {
+ if (origin.content.isEmpty()) continue
+ appendParagraph {
+ appendStrong { to.append("Platform and version requirements:") }
+ to.append(" " + origin.actualPlatforms)
+ appendContent(origin.summary)
+ }
+ }
+ } else {
+ if (!item.content.isEmpty()) {
+ appendStrong { to.append("Platform and version requirements:") }
+ to.append(" " + item.actualPlatforms)
+ appendContent(item.content.summary)
+ }
+ }
+
item.appendDescription()
}
+
+ fun renderSimpleNode(item: DocumentationNode, isSingleNode: Boolean) {
+ // TODO: use summarizesignatures
+ val rendered = languageService.render(item)
+ item.detailOrNull(NodeKind.Signature)?.let {
+ if (item.kind !in NodeKind.classLike || !isSingleNode)
+ appendAnchor(it.name)
+ }
+ appendAsSignature(rendered) {
+ appendCode { appendContent(rendered) }
+ item.appendSourceLink()
+ }
+ item.appendOverrides()
+ item.appendDeprecation()
+ item.appendPlatforms()
+ }
+
+ fun renderGroupNode(item: DocumentationNode, isSingleNode: Boolean) {
+ // TODO: use summarizesignatures
+ val groupBySignature = item.origins.groupBy {
+ languageService.render(it)
+ }
+
+ for ((sign, nodes) in groupBySignature) {
+ val first = nodes.first()
+ first.detailOrNull(NodeKind.Signature)?.let {
+ if (item.kind !in NodeKind.classLike || !isSingleNode)
+ appendAnchor(it.name)
+ }
+
+ appendAsSignature(sign) {
+ appendCode { appendContent(sign) }
+ }
+ first.appendOverrides()
+ first.appendDeprecation()
+
+
+ appendParagraph {
+ appendStrong { to.append("Platform and version requirements:") }
+ to.append(" " + nodes
+ .flatMap { it.actualPlatforms }
+ .distinct()
+ .joinToString()
+ )
+ }
+
+ }
+ }
+
private fun DocumentationNode.appendSourceLink() {
val sourceUrl = details(NodeKind.SourceUrl).firstOrNull()
if (sourceUrl != null) {
@@ -349,7 +419,8 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
overrides.forEach {
appendParagraph {
to.append("Overrides ")
- val location = locationService.relativePathToLocation(this, it)
+ val location = location().relativePathTo(it.location())
+
appendLink(FormatLink(it.owner!!.name + "." + it.name, location))
}
}
@@ -360,30 +431,30 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
val deprecationParameter = deprecation!!.details(NodeKind.Parameter).firstOrNull()
val deprecationValue = deprecationParameter?.details(NodeKind.Value)?.firstOrNull()
appendLine()
- if (deprecationValue != null) {
- appendStrong { to.append("Deprecated:") }
- appendText(" " + deprecationValue.name.removeSurrounding("\""))
- appendLine()
- appendLine()
- } else if (deprecation?.content != Content.Empty) {
- appendStrong { to.append("Deprecated:") }
- to.append(" ")
- appendContent(deprecation!!.content)
- } else {
- appendStrong { to.append("Deprecated") }
- appendLine()
- appendLine()
+ when {
+ deprecationValue != null -> {
+ appendStrong { to.append("Deprecated:") }
+ appendText(" " + deprecationValue.name.removeSurrounding("\""))
+ appendLine()
+ appendLine()
+ }
+ deprecation?.content != Content.Empty -> {
+ appendStrong { to.append("Deprecated:") }
+ to.append(" ")
+ appendContent(deprecation!!.content)
+ }
+ else -> {
+ appendStrong { to.append("Deprecated") }
+ appendLine()
+ appendLine()
+ }
}
}
}
private fun DocumentationNode.appendPlatforms() {
- val platforms = if (isModuleOrPackage())
- platformsToShow.toSet() + platformsOfItems(members)
- else
- platformsToShow
-
- if(platforms.isEmpty()) return
+ val platforms = actualPlatforms
+ if (platforms.isEmpty()) return
appendParagraph {
appendStrong { to.append("Platform and version requirements:") }
@@ -391,16 +462,39 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
+ val DocumentationNode.actualPlatforms: Collection<String>
+ get() = if (isModuleOrPackage())
+ platformsToShow.toSet() + platformsOfItems(members)
+ else
+ platformsToShow
+
+
+ protected fun mergeVersions(otherKotlinVersion: String, kotlinVersions: List<String>): String {
+ val allKotlinVersions = (kotlinVersions + otherKotlinVersion).distinct()
+
+ val minVersion = allKotlinVersions.min()!!
+ val resultVersion: String = when {
+ allKotlinVersions.size == 1 -> allKotlinVersions.single()
+ minVersion.endsWith("+") -> minVersion
+ else -> "$minVersion+"
+ }
+
+ return resultVersion
+ }
+
protected fun platformsOfItems(items: List<DocumentationNode>): Set<String> {
val platforms = items.asSequence().map {
when (it.kind) {
- NodeKind.ExternalClass, NodeKind.Package, NodeKind.Module, NodeKind.GroupNode -> platformsOfItems(it.members)
+ NodeKind.ExternalClass, NodeKind.Package, NodeKind.Module -> platformsOfItems(it.members)
+ NodeKind.GroupNode -> platformsOfItems(it.origins)
else -> it.platformsToShow.toSet()
}
}
fun String.isKotlinVersion() = this.startsWith("Kotlin")
+ if (platforms.count() == 0) return emptySet()
+
// Calculating common platforms for items
return platforms.reduce { result, platformsOfItem ->
val otherKotlinVersion = result.find { it.isKotlinVersion() }
@@ -408,19 +502,38 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
// When no Kotlin version specified, it means that version is 1.0
if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) {
- val allKotlinVersions = (kotlinVersions + otherKotlinVersion).distinct()
+ result.intersect(platformsOfItem) + mergeVersions(otherKotlinVersion, kotlinVersions)
+ } else {
+ result.intersect(platformsOfItem)
+ }
+ }
+ }
- val minVersion = allKotlinVersions.min()!!
- val resultVersion = when {
- allKotlinVersions.size == 1 -> allKotlinVersions.single()
- minVersion.endsWith("+") -> minVersion
- else -> minVersion + "+"
- }
+ protected fun unionPlatformsOfItems(items: List<DocumentationNode>): Set<String> {
+ val platforms = items.asSequence().map {
+ when (it.kind) {
+ NodeKind.GroupNode -> unionPlatformsOfItems(it.origins)
+ else -> it.platformsToShow.toSet()
+ }
+ }
+
+ fun String.isKotlinVersion() = this.startsWith("Kotlin")
+
+ if (platforms.count() == 0) return emptySet()
+
+ // Calculating common platforms for items
+ return platforms.reduce { result, platformsOfItem ->
+ val otherKotlinVersion = result.find { it.isKotlinVersion() }
+ val (kotlinVersions, otherPlatforms) = platformsOfItem.partition { it.isKotlinVersion() }
- result.intersect(otherPlatforms) + resultVersion
+ // When no Kotlin version specified, it means that version is 1.0
+ if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) {
+ result.union(otherPlatforms) + mergeVersions(otherKotlinVersion, kotlinVersions)
} else {
- result.intersect(platformsOfItem)
+ result.union(otherPlatforms)
}
+ }.let {
+ if (it.containsAll(impliedPlatforms)) it - impliedPlatforms else it
}
}
@@ -456,6 +569,15 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
+ inner class SingleNodePageBuilder(val node: DocumentationNode, noHeader: Boolean = false) :
+ PageBuilder(listOf(node), noHeader) {
+
+ override fun build() {
+ super.build()
+ SectionsBuilder(node).build()
+ }
+ }
+
inner class GroupNodePageBuilder(val node: DocumentationNode) : PageBuilder(listOf(node)) {
override fun build() {
@@ -466,39 +588,40 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
appendLine()
appendHeader { appendText(node.name) }
- fun DocumentationNode.priority(): Int = when (kind) {
- NodeKind.TypeAlias -> 1
- NodeKind.Class -> 2
- else -> 3
- }
+ renderGroupNode(node, true)
- for (member in node.members.sortedBy(DocumentationNode::priority)) {
+ for (origin in node.origins) {
+ if (origin.content.isEmpty()) continue
+ appendStrong { to.append("Platform and version requirements:") }
+ to.append(" " + origin.actualPlatforms)
+ appendContent(origin.content.summary)
+ }
- appendAsOverloadGroup(to, platformsOfItems(listOf(member))) {
- formatSubNodeOfGroup(member)
- }
+ SectionsBuilder(node).build()
+ }
+ }
+ private fun unionPlatformsOfItems(items: List<DocumentationNode>): Set<String> {
+ val platforms = items.flatMapTo(mutableSetOf<String>()) {
+ when (it.kind) {
+ NodeKind.GroupNode -> unionPlatformsOfItems(it.origins)
+ else -> it.platforms
}
}
- fun formatSubNodeOfGroup(member: DocumentationNode) {
- SingleNodePageBuilder(member, true).build()
- }
+ return platforms.let { if (it.containsAll(impliedPlatforms)) it - impliedPlatforms else it }
}
- inner class SingleNodePageBuilder(val node: DocumentationNode, noHeader: Boolean = false)
- : PageBuilder(listOf(node), noHeader) {
+ inner class SectionsBuilder(val node: DocumentationNode): PageBuilder(listOf(node)) {
override fun build() {
- super.build()
-
if (node.kind == NodeKind.ExternalClass) {
appendSection("Extensions for ${node.name}", node.members)
return
}
fun DocumentationNode.membersOrGroupMembers(predicate: (DocumentationNode) -> Boolean): List<DocumentationNode> {
- return members.filter(predicate) + members(NodeKind.GroupNode).flatMap { it.members.filter(predicate) }
+ return members.filter(predicate) + members(NodeKind.GroupNode).filter{ it.origins.isNotEmpty() && predicate(it.origins.first()) }
}
fun DocumentationNode.membersOrGroupMembers(kind: NodeKind): List<DocumentationNode> {
@@ -506,20 +629,19 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
appendSection("Packages", node.members(NodeKind.Package), platformsBasedOnMembers = true)
- appendSection("Types", node.membersOrGroupMembers { it.kind in NodeKind.classLike && it.kind != NodeKind.TypeAlias && it.kind != NodeKind.AnnotationClass && it.kind != NodeKind.Exception })
+ appendSection("Types", node.membersOrGroupMembers { it.kind in NodeKind.classLike /*&& it.kind != NodeKind.TypeAlias*/ && it.kind != NodeKind.AnnotationClass && it.kind != NodeKind.Exception })
appendSection("Annotations", node.membersOrGroupMembers(NodeKind.AnnotationClass))
appendSection("Exceptions", node.membersOrGroupMembers(NodeKind.Exception))
- appendSection("Type Aliases", node.membersOrGroupMembers(NodeKind.TypeAlias))
appendSection("Extensions for External Classes", node.members(NodeKind.ExternalClass))
- appendSection("Enum Values", node.members(NodeKind.EnumItem), sortMembers = false, omitSamePlatforms = true)
- appendSection("Constructors", node.members(NodeKind.Constructor), omitSamePlatforms = true)
- appendSection("Properties", node.members(NodeKind.Property), omitSamePlatforms = true)
+ appendSection("Enum Values", node.membersOrGroupMembers(NodeKind.EnumItem), sortMembers = false, omitSamePlatforms = true)
+ appendSection("Constructors", node.membersOrGroupMembers(NodeKind.Constructor), omitSamePlatforms = true)
+ appendSection("Properties", node.membersOrGroupMembers(NodeKind.Property), omitSamePlatforms = true)
appendSection("Inherited Properties", node.inheritedMembers(NodeKind.Property))
- appendSection("Functions", node.members(NodeKind.Function), omitSamePlatforms = true)
+ appendSection("Functions", node.membersOrGroupMembers(NodeKind.Function), omitSamePlatforms = true)
appendSection("Inherited Functions", node.inheritedMembers(NodeKind.Function))
- appendSection("Companion Object Properties", node.members(NodeKind.CompanionObjectProperty), omitSamePlatforms = true)
+ appendSection("Companion Object Properties", node.membersOrGroupMembers(NodeKind.CompanionObjectProperty), omitSamePlatforms = true)
appendSection("Inherited Companion Object Properties", node.inheritedCompanionObjectMembers(NodeKind.Property))
- appendSection("Companion Object Functions", node.members(NodeKind.CompanionObjectFunction), omitSamePlatforms = true)
+ appendSection("Companion Object Functions", node.membersOrGroupMembers(NodeKind.CompanionObjectFunction), omitSamePlatforms = true)
appendSection("Inherited Companion Object Functions", node.inheritedCompanionObjectMembers(NodeKind.Function))
appendSection("Other members", node.members.filter {
it.kind !in setOf(
@@ -554,7 +676,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
if (node.kind == NodeKind.Module) {
appendHeader(3) { to.append("Index") }
node.members(NodeKind.AllTypes).singleOrNull()?.let { allTypes ->
- appendLink(link(node, allTypes, { "All Types" }))
+ appendLink(link(node, allTypes) { "All Types" })
}
}
}
@@ -567,7 +689,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
appendHeader(3) { appendText(caption) }
- val children = if (sortMembers) members.sortedBy { it.name } else members
+ val children = if (sortMembers) members.sortedBy { it.name.toLowerCase() } else members
val membersMap = children.groupBy { link(node, it) }
@@ -590,11 +712,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
appendTableCell {
- val breakdownBySummary = members.groupBy { it.summary }
- for ((summary, items) in breakdownBySummary) {
- appendSummarySignatures(items)
- appendContent(summary)
- }
+ appendSummarySignatures(members, platformsBasedOnMembers, omitSamePlatforms)
}
}
}
@@ -603,6 +721,10 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
private fun platformsOfItems(items: List<DocumentationNode>, omitSamePlatforms: Boolean = true): Set<String> {
+ if (items.all { it.kind != NodeKind.Package && it.kind != NodeKind.Module && it.kind != NodeKind.ExternalClass }) {
+ return unionPlatformsOfItems(items)
+ }
+
val platforms = platformsOfItems(items)
if (platforms.isNotEmpty() && (platforms != node.platformsToShow.toSet() || !omitSamePlatforms)) {
return platforms
@@ -610,27 +732,62 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
return emptySet()
}
- private fun appendSummarySignatures(items: List<DocumentationNode>) {
- val summarySignature = languageService.summarizeSignatures(items)
- if (summarySignature != null) {
- appendAsSignature(summarySignature) {
- summarySignature.appendSignature()
+ private fun appendSummarySignatures(items: List<DocumentationNode>, platformsBasedOnMembers: Boolean, omitSamePlatforms: Boolean) {
+ val groupBySummary = items.groupBy { it.summary }
+
+ for ((summary, node) in groupBySummary) {
+ val nodesToAppend = if (node.all { it.kind == NodeKind.GroupNode }) {
+ node.flatMap { it.origins }
+ } else {
+ node
}
- return
- }
- val renderedSignatures = items.map { languageService.render(it, RenderMode.SUMMARY) }
- renderedSignatures.subList(0, renderedSignatures.size - 1).forEach {
- appendAsSignature(it) {
- it.appendSignature()
+
+ val summarySignature = languageService.summarizeSignatures(nodesToAppend)
+ if (summarySignature != null) {
+ appendSignatures(summarySignature, items, platformsBasedOnMembers, omitSamePlatforms)
+ } else {
+ val groupBySignature = nodesToAppend.groupBy {
+ languageService.render(it, RenderMode.SUMMARY)
+ }
+ for ((sign, members) in groupBySignature) {
+ appendSignatures(sign, members, platformsBasedOnMembers, omitSamePlatforms)
+ }
+ }
+
+ val platforms = platformsOfItems(node)
+ appendAsBlockWithPlatforms(platforms) {
+ appendContent(summary)
+ appendSoftLineBreak()
}
- appendLine()
}
- appendAsSignature(renderedSignatures.last()) {
- renderedSignatures.last().appendSignature()
+ }
+
+ private fun appendSignatures(signature: ContentNode, items: List<DocumentationNode>, platformsBasedOnMembers: Boolean, omitSamePlatforms: Boolean) {
+ val elementPlatforms = platformsOfItems(items, omitSamePlatforms)
+ val platforms = if (platformsBasedOnMembers)
+ items.flatMapTo(mutableSetOf()) { platformsOfItems(it.members) } + elementPlatforms
+ else
+ elementPlatforms
+
+ appendAsBlockWithPlatforms(platforms) {
+ appendPlatforms(platforms)
+ appendAsSignature(signature) {
+ signature.appendSignature()
+ }
+ appendSoftLineBreak()
}
}
}
+ private fun DocumentationNode.isClassLikeGroupNode(): Boolean {
+ if (kind != NodeKind.GroupNode) {
+ return false
+ }
+
+ return origins.all { it.kind in NodeKind.classLike }
+ }
+
+
inner class AllTypesNodeBuilder(val node: DocumentationNode)
: PageBuilder(listOf(node)) {
@@ -654,7 +811,13 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
appendTableCell {
- appendContent(type.summary)
+ val summary = if (type.isClassLikeGroupNode()) {
+ type.origins.first().summary
+ } else {
+ type.summary
+ }
+
+ appendContent(summary)
}
}
}
@@ -674,9 +837,9 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
-abstract class StructuredFormatService(locationService: LocationService,
+abstract class StructuredFormatService(val generator: NodeLocationAwareGenerator,
val languageService: LanguageService,
override val extension: String,
override final val linkExtension: String = extension) : FormatService {
- val locationService: LocationService = locationService.withExtension(linkExtension)
+
}
diff --git a/core/src/main/kotlin/Formats/YamlOutlineService.kt b/core/src/main/kotlin/Formats/YamlOutlineService.kt
index 7968824c..c36f98eb 100644
--- a/core/src/main/kotlin/Formats/YamlOutlineService.kt
+++ b/core/src/main/kotlin/Formats/YamlOutlineService.kt
@@ -3,15 +3,17 @@ package org.jetbrains.dokka
import com.google.inject.Inject
import java.io.File
-class YamlOutlineService @Inject constructor(val locationService: LocationService,
- val languageService: LanguageService) : OutlineFormatService {
+class YamlOutlineService @Inject constructor(
+ val generator: NodeLocationAwareGenerator,
+ val languageService: LanguageService
+) : OutlineFormatService {
override fun getOutlineFileName(location: Location): File = File("${location.path}.yml")
var outlineLevel = 0
override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) {
val indent = " ".repeat(outlineLevel)
to.appendln("$indent- title: ${languageService.renderName(node)}")
- to.appendln("$indent url: ${locationService.location(node).path}")
+ to.appendln("$indent url: ${generator.location(node).path}")
}
override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
diff --git a/core/src/main/kotlin/Generation/ConsoleGenerator.kt b/core/src/main/kotlin/Generation/ConsoleGenerator.kt
deleted file mode 100644
index 301f86b9..00000000
--- a/core/src/main/kotlin/Generation/ConsoleGenerator.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.jetbrains.dokka
-
-class ConsoleGenerator(val signatureGenerator: LanguageService, val locationService: LocationService) {
- val IndentStep = " "
-
- fun generate(node: DocumentationNode, indent: String = "") {
- println("@${locationService.location(node).path}")
- generateHeader(node, indent)
- //generateDetails(node, indent)
- generateMembers(node, indent)
- generateLinks(node, indent)
- }
-
- fun generateHeader(node: DocumentationNode, indent: String = "") {
- println(indent + signatureGenerator.render(node))
- val docString = node.content.toString()
- if (!docString.isEmpty())
- println("$indent\"${docString.replace("\n", "\n$indent")}\"")
- println()
- }
-
- fun generateMembers(node: DocumentationNode, indent: String = "") {
- val items = node.members.sortedBy { it.name }
- for (child in items)
- generate(child, indent + IndentStep)
- }
-
- fun generateDetails(node: DocumentationNode, indent: String = "") {
- val items = node.details
- for (child in items)
- generate(child, indent + " ")
- }
-
- fun generateLinks(node: DocumentationNode, indent: String = "") {
- val items = node.links
- if (items.isEmpty())
- return
- println("$indent Links")
- for (child in items)
- generate(child, indent + " ")
- }
-} \ No newline at end of file
diff --git a/core/src/main/kotlin/Generation/DocumentationMerger.kt b/core/src/main/kotlin/Generation/DocumentationMerger.kt
new file mode 100644
index 00000000..0394dce2
--- /dev/null
+++ b/core/src/main/kotlin/Generation/DocumentationMerger.kt
@@ -0,0 +1,180 @@
+package org.jetbrains.dokka.Generation
+
+import org.jetbrains.dokka.*
+
+class DocumentationMerger(
+ private val documentationModules: List<DocumentationModule>
+) {
+ private val producedNodeRefGraph: NodeReferenceGraph = NodeReferenceGraph()
+ private val signatureMap: Map<DocumentationNode, String>
+ private val oldToNewNodeMap: MutableMap<DocumentationNode, DocumentationNode> = mutableMapOf()
+
+ init {
+ if (documentationModules.groupBy { it.name }.size > 1) {
+ throw IllegalArgumentException("Modules should have similar names")
+ }
+
+ signatureMap = documentationModules
+ .flatMap { it.nodeRefGraph.nodeMapView.entries }
+ .associate { (k, v) -> v to k }
+
+
+ documentationModules.map { it.nodeRefGraph }
+ .flatMap { it.references }
+ .forEach { producedNodeRefGraph.addReference(it) }
+ }
+
+ private fun mergePackageReferences(
+ from: DocumentationNode,
+ packages: List<DocumentationReference>
+ ): List<DocumentationReference> {
+ val packagesByName = packages
+ .map { it.to }
+ .groupBy { it.name }
+
+ val resultReferences = mutableListOf<DocumentationReference>()
+ for ((name, listOfPackages) in packagesByName) {
+ val producedPackage = mergePackagesWithEqualNames(name, from, listOfPackages)
+ updatePendingReferences()
+
+ resultReferences.add(
+ DocumentationReference(from, producedPackage, RefKind.Member)
+ )
+ }
+
+ return resultReferences
+ }
+
+ private fun mergePackagesWithEqualNames(
+ name: String,
+ from: DocumentationNode,
+ packages: List<DocumentationNode>
+ ): DocumentationNode {
+ val mergedPackage = DocumentationNode(name, Content.Empty, NodeKind.Package)
+
+ for (contentToAppend in packages.map { it.content }.distinct()) {
+ mergedPackage.updateContent {
+ for (otherChild in contentToAppend.children) {
+ children.add(otherChild)
+ }
+ }
+ }
+
+ for (node in packages) {
+ oldToNewNodeMap[node] = mergedPackage
+ }
+
+ val references = packages.flatMap { it.allReferences() }
+ val mergedReferences = mergeReferences(mergedPackage, references)
+ for (ref in mergedReferences) {
+ if (ref.kind == RefKind.Owner) {
+ continue
+ }
+ mergedPackage.addReference(ref)
+ }
+
+ from.append(mergedPackage, RefKind.Member)
+
+ return mergedPackage
+ }
+
+ private fun mergeMemberReferences(
+ from: DocumentationNode,
+ refs: List<DocumentationReference>
+ ): List<DocumentationReference> {
+ val membersBySignature: Map<String, List<DocumentationNode>> = refs.map { it.to }
+ .groupBy { signatureMap[it]!! }
+
+ val mergedMembers: MutableList<DocumentationReference> = mutableListOf()
+ for ((signature, members) in membersBySignature) {
+ val newNode = mergeMembersWithEqualSignature(signature, members)
+
+ producedNodeRefGraph.register(signature, newNode)
+ updatePendingReferences()
+ from.append(newNode, RefKind.Member)
+
+ mergedMembers.add(DocumentationReference(from, newNode, RefKind.Member))
+ }
+
+ return mergedMembers
+ }
+
+ private fun mergeMembersWithEqualSignature(
+ signature: String,
+ nodes: List<DocumentationNode>
+ ): DocumentationNode {
+ val singleNode = nodes.singleOrNull()
+ if (singleNode != null) {
+ singleNode.dropReferences { it.kind == RefKind.Owner }
+ return singleNode
+ }
+
+ val groupNode = DocumentationNode(nodes.first().name, Content.Empty, NodeKind.GroupNode)
+ groupNode.appendTextNode(signature, NodeKind.Signature, RefKind.Detail)
+
+ for (node in nodes) {
+ node.dropReferences { it.kind == RefKind.Owner }
+ groupNode.append(node, RefKind.Origin)
+
+ oldToNewNodeMap[node] = groupNode
+ }
+
+ // if nodes are classes, nested members should be also merged and
+ // inserted at the same level with class
+ if (nodes.all { it.kind == NodeKind.Class }) {
+ val members = nodes.flatMap { it.allReferences() }.filter { it.kind == RefKind.Member }
+ val mergedMembers = mergeMemberReferences(groupNode, members)
+
+ for (ref in mergedMembers) {
+ if (ref.kind == RefKind.Owner) {
+ continue
+ }
+
+ groupNode.append(ref.to, RefKind.Member)
+ }
+ }
+
+ return groupNode
+ }
+
+
+ private fun mergeReferences(
+ from: DocumentationNode,
+ refs: List<DocumentationReference>
+ ): List<DocumentationReference> {
+ val (refsToPackages, otherRefs) = refs.partition { it.to.kind == NodeKind.Package }
+ val mergedPackages = mergePackageReferences(from, refsToPackages)
+
+ val (refsToMembers, refsNotToMembers) = otherRefs.partition { it.kind == RefKind.Member }
+ val mergedMembers = mergeMemberReferences(from, refsToMembers)
+
+ return mergedPackages + mergedMembers + refsNotToMembers
+ }
+
+ fun merge(): DocumentationModule {
+ val mergedDocumentationModule = DocumentationModule(
+ name = documentationModules.first().name,
+ nodeRefGraph = producedNodeRefGraph
+ )
+
+ val refs = documentationModules.flatMap {
+ it.allReferences()
+ }
+ mergeReferences(mergedDocumentationModule, refs)
+
+ return mergedDocumentationModule
+ }
+
+ private fun updatePendingReferences() {
+ for (ref in producedNodeRefGraph.references) {
+ ref.lazyNodeFrom.update()
+ ref.lazyNodeTo.update()
+ }
+ }
+
+ private fun NodeResolver.update() {
+ if (this is NodeResolver.Exact && exactNode in oldToNewNodeMap) {
+ exactNode = oldToNewNodeMap[exactNode]!!
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Generation/DokkaGenerator.kt b/core/src/main/kotlin/Generation/DokkaGenerator.kt
index 09e5cedf..1193657e 100644
--- a/core/src/main/kotlin/Generation/DokkaGenerator.kt
+++ b/core/src/main/kotlin/Generation/DokkaGenerator.kt
@@ -7,9 +7,10 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
-import org.jetbrains.dokka.DokkaConfiguration.SourceRoot
+import org.jetbrains.dokka.Generation.DocumentationMerger
import org.jetbrains.dokka.Utilities.DokkaAnalysisModule
import org.jetbrains.dokka.Utilities.DokkaOutputModule
+import org.jetbrains.dokka.Utilities.DokkaRunModule
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
@@ -24,57 +25,64 @@ import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import kotlin.system.measureTimeMillis
-class DokkaGenerator(val logger: DokkaLogger,
- val classpath: List<String>,
- val sources: List<SourceRoot>,
- val samples: List<String>,
- val includes: List<String>,
- val moduleName: String,
- val options: DocumentationOptions) {
+class DokkaGenerator(val dokkaConfiguration: DokkaConfiguration,
+ val logger: DokkaLogger) {
- private val documentationModule = DocumentationModule(moduleName)
+ private val documentationModules: MutableList<DocumentationModule> = mutableListOf()
+ private val globalInjector = Guice.createInjector(DokkaRunModule(dokkaConfiguration))
- fun generate() {
- val sourcesGroupedByPlatform = sources.groupBy { it.platforms.firstOrNull() }
- for ((platform, roots) in sourcesGroupedByPlatform) {
- appendSourceModule(platform, roots)
+
+ fun generate() = with(dokkaConfiguration) {
+
+
+ for (pass in passesConfigurations) {
+ val documentationModule = DocumentationModule(pass.moduleName)
+ appendSourceModule(pass, documentationModule)
+ documentationModules.add(documentationModule)
}
- documentationModule.prepareForGeneration(options)
+
+ val totalDocumentationModule = DocumentationMerger(documentationModules).merge()
+ totalDocumentationModule.prepareForGeneration(dokkaConfiguration)
val timeBuild = measureTimeMillis {
logger.info("Generating pages... ")
- val outputInjector = Guice.createInjector(DokkaOutputModule(options, logger))
- outputInjector.getInstance(Generator::class.java).buildAll(documentationModule)
+ val outputInjector = globalInjector.createChildInjector(DokkaOutputModule(dokkaConfiguration, logger))
+ val instance = outputInjector.getInstance(Generator::class.java)
+ instance.buildAll(totalDocumentationModule)
}
logger.info("done in ${timeBuild / 1000} secs")
}
- private fun appendSourceModule(defaultPlatform: String?, sourceRoots: List<SourceRoot>) {
- val sourcePaths = sourceRoots.map { it.path }
- val environment = createAnalysisEnvironment(sourcePaths)
+ private fun appendSourceModule(
+ passConfiguration: DokkaConfiguration.PassConfiguration,
+ documentationModule: DocumentationModule
+ ) = with(passConfiguration) {
+
+ val sourcePaths = passConfiguration.sourceRoots.map { it.path }
+ val environment = createAnalysisEnvironment(sourcePaths, passConfiguration)
logger.info("Module: $moduleName")
- logger.info("Output: ${File(options.outputDir)}")
+ logger.info("Output: ${File(dokkaConfiguration.outputDir)}")
logger.info("Sources: ${sourcePaths.joinToString()}")
logger.info("Classpath: ${environment.classpath.joinToString()}")
logger.info("Analysing sources and libraries... ")
val startAnalyse = System.currentTimeMillis()
- val defaultPlatformAsList = defaultPlatform?.let { listOf(it) }.orEmpty()
+ val defaultPlatformAsList = passConfiguration.targets
val defaultPlatformsProvider = object : DefaultPlatformsProvider {
override fun getDefaultPlatforms(descriptor: DeclarationDescriptor): List<String> {
val containingFilePath = descriptor.sourcePsi()?.containingFile?.virtualFile?.canonicalPath
?.let { File(it).absolutePath }
val sourceRoot = containingFilePath?.let { path -> sourceRoots.find { path.startsWith(it.path) } }
- return sourceRoot?.platforms ?: defaultPlatformAsList
+ return /*sourceRoot?.platforms ?: */defaultPlatformAsList
}
}
- val injector = Guice.createInjector(
- DokkaAnalysisModule(environment, options, defaultPlatformsProvider, documentationModule.nodeRefGraph, logger))
+ val injector = globalInjector.createChildInjector(
+ DokkaAnalysisModule(environment, dokkaConfiguration, defaultPlatformsProvider, documentationModule.nodeRefGraph, passConfiguration, logger))
- buildDocumentationModule(injector, documentationModule, { isNotSample(it) }, includes)
+ buildDocumentationModule(injector, documentationModule, { isNotSample(it, passConfiguration.samples) }, includes)
val timeAnalyse = System.currentTimeMillis() - startAnalyse
logger.info("done in ${timeAnalyse / 1000} secs")
@@ -82,26 +90,31 @@ class DokkaGenerator(val logger: DokkaLogger,
Disposer.dispose(environment)
}
- fun createAnalysisEnvironment(sourcePaths: List<String>): AnalysisEnvironment {
- val environment = AnalysisEnvironment(DokkaMessageCollector(logger))
+ fun createAnalysisEnvironment(
+ sourcePaths: List<String>,
+ passConfiguration: DokkaConfiguration.PassConfiguration
+ ): AnalysisEnvironment {
+ val environment = AnalysisEnvironment(DokkaMessageCollector(logger), passConfiguration.analysisPlatform)
environment.apply {
- addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
+ if (analysisPlatform == Platform.jvm) {
+ addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
+ }
// addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
- for (element in this@DokkaGenerator.classpath) {
+ for (element in passConfiguration.classpath) {
addClasspath(File(element))
}
addSources(sourcePaths)
- addSources(this@DokkaGenerator.samples)
+ addSources(passConfiguration.samples)
- loadLanguageVersionSettings(options.languageVersion, options.apiVersion)
+ loadLanguageVersionSettings(passConfiguration.languageVersion, passConfiguration.apiVersion)
}
return environment
}
- fun isNotSample(file: PsiFile): Boolean {
+ private fun isNotSample(file: PsiFile, samples: List<String>): Boolean {
val sourceFile = File(file.virtualFile!!.path)
return samples.none { sample ->
val canonicalSample = File(sample).canonicalPath
@@ -140,9 +153,7 @@ fun buildDocumentationModule(injector: Injector,
val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzer::class.java)
analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles)
- val fragments = fragmentFiles
- .map { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) }
- .filterNotNull()
+ val fragments = fragmentFiles.mapNotNull { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) }
.distinct()
val packageDocs = injector.getInstance(PackageDocs::class.java)
@@ -157,9 +168,13 @@ fun buildDocumentationModule(injector: Injector,
}
}
+ parseJavaPackageDocs(packageDocs, coreEnvironment)
+
with(injector.getInstance(DocumentationBuilder::class.java)) {
documentationModule.appendFragments(fragments, packageDocs.packageContent,
injector.getInstance(PackageDocumentationBuilder::class.java))
+
+ propagateExtensionFunctionsToSubclasses(fragments, resolutionFacade)
}
val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter)
@@ -168,6 +183,18 @@ fun buildDocumentationModule(injector: Injector,
}
}
+fun parseJavaPackageDocs(packageDocs: PackageDocs, coreEnvironment: KotlinCoreEnvironment) {
+ val contentRoots = coreEnvironment.configuration.get(JVMConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<JavaSourceRoot>()
+ ?.map { it.file }
+ ?: listOf()
+ contentRoots.forEach { root ->
+ root.walkTopDown().filter { it.name == "overview.html" }.forEach {
+ packageDocs.parseJava(it.path, it.relativeTo(root).parent.replace("/", "."))
+ }
+ }
+}
+
fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> {
val sourceRoots = configuration.get(JVMConfigurationKeys.CONTENT_ROOTS)
@@ -189,4 +216,4 @@ fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> {
}
}
return result
-}
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Generation/FileGenerator.kt b/core/src/main/kotlin/Generation/FileGenerator.kt
index e055c537..eb6800b3 100644
--- a/core/src/main/kotlin/Generation/FileGenerator.kt
+++ b/core/src/main/kotlin/Generation/FileGenerator.kt
@@ -1,28 +1,40 @@
package org.jetbrains.dokka
import com.google.inject.Inject
+import com.google.inject.name.Named
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
-class FileGenerator @Inject constructor(val locationService: FileLocationService) : Generator {
+class FileGenerator @Inject constructor(@Named("outputDir") override val root: File) : NodeLocationAwareGenerator {
@set:Inject(optional = true) var outlineService: OutlineFormatService? = null
@set:Inject(optional = true) lateinit var formatService: FormatService
- @set:Inject(optional = true) lateinit var options: DocumentationOptions
+ @set:Inject(optional = true) lateinit var dokkaConfiguration: DokkaConfiguration
@set:Inject(optional = true) var packageListService: PackageListService? = null
+ override fun location(node: DocumentationNode): FileLocation {
+ return FileLocation(fileForNode(node, formatService.linkExtension))
+ }
+
+ private fun fileForNode(node: DocumentationNode, extension: String = ""): File {
+ return File(root, relativePathToNode(node)).appendExtension(extension)
+ }
+
+ fun locationWithoutExtension(node: DocumentationNode): FileLocation {
+ return FileLocation(fileForNode(node))
+ }
+
override fun buildPages(nodes: Iterable<DocumentationNode>) {
- val specificLocationService = locationService.withExtension(formatService.extension)
- for ((location, items) in nodes.groupBy { specificLocationService.location(it) }) {
- val file = location.file
+ for ((file, items) in nodes.groupBy { fileForNode(it, formatService.extension) }) {
+
file.parentFile?.mkdirsOrFail()
try {
FileOutputStream(file).use {
OutputStreamWriter(it, Charsets.UTF_8).use {
- it.write(formatService.format(location, items))
+ it.write(formatService.format(location(items.first()), items))
}
}
} catch (e: Throwable) {
@@ -34,7 +46,7 @@ class FileGenerator @Inject constructor(val locationService: FileLocationService
override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
val outlineService = this.outlineService ?: return
- for ((location, items) in nodes.groupBy { locationService.location(it) }) {
+ for ((location, items) in nodes.groupBy { locationWithoutExtension(it) }) {
val file = outlineService.getOutlineFileName(location)
file.parentFile?.mkdirsOrFail()
FileOutputStream(file).use {
@@ -47,7 +59,7 @@ class FileGenerator @Inject constructor(val locationService: FileLocationService
override fun buildSupportFiles() {
formatService.enumerateSupportFiles { resource, targetPath ->
- FileOutputStream(locationService.location(listOf(targetPath), false).file).use {
+ FileOutputStream(File(root, relativePathToNode(listOf(targetPath), false))).use {
javaClass.getResourceAsStream(resource).copyTo(it)
}
}
@@ -58,10 +70,10 @@ class FileGenerator @Inject constructor(val locationService: FileLocationService
for (module in nodes) {
- val moduleRoot = locationService.location(module).file.parentFile
+ val moduleRoot = location(module).file.parentFile
val packageListFile = File(moduleRoot, "package-list")
- packageListFile.writeText("\$dokka.format:${options.outputFormat}\n" +
+ packageListFile.writeText("\$dokka.format:${dokkaConfiguration.format}\n" +
packageListService!!.formatPackageList(module as DocumentationModule))
}
diff --git a/core/src/main/kotlin/Generation/Generator.kt b/core/src/main/kotlin/Generation/Generator.kt
index 76a5f350..23286e29 100644
--- a/core/src/main/kotlin/Generation/Generator.kt
+++ b/core/src/main/kotlin/Generation/Generator.kt
@@ -1,5 +1,7 @@
package org.jetbrains.dokka
+import java.io.File
+
interface Generator {
fun buildPages(nodes: Iterable<DocumentationNode>)
fun buildOutlines(nodes: Iterable<DocumentationNode>)
@@ -19,3 +21,9 @@ fun Generator.buildPage(node: DocumentationNode): Unit = buildPages(listOf(node)
fun Generator.buildOutline(node: DocumentationNode): Unit = buildOutlines(listOf(node))
fun Generator.buildAll(node: DocumentationNode): Unit = buildAll(listOf(node))
+
+
+interface NodeLocationAwareGenerator: Generator {
+ fun location(node: DocumentationNode): Location
+ val root: File
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Generation/configurationImpl.kt b/core/src/main/kotlin/Generation/configurationImpl.kt
index 34d4154e..2aa0e0d7 100644
--- a/core/src/main/kotlin/Generation/configurationImpl.kt
+++ b/core/src/main/kotlin/Generation/configurationImpl.kt
@@ -18,14 +18,11 @@ data class SourceLinkDefinitionImpl(override val path: String,
}
}
-class SourceRootImpl(path: String, override val platforms: List<String> = emptyList()) : SourceRoot {
+class SourceRootImpl(path: String) : SourceRoot {
override val path: String = File(path).absolutePath
companion object {
- fun parseSourceRoot(sourceRoot: String): SourceRoot {
- val components = sourceRoot.split("::", limit = 2)
- return SourceRootImpl(components.last(), if (components.size == 1) listOf() else components[0].split(','))
- }
+ fun parseSourceRoot(sourceRoot: String): SourceRoot = SourceRootImpl(sourceRoot)
}
}
@@ -36,27 +33,47 @@ data class PackageOptionsImpl(override val prefix: String,
override val suppress: Boolean = false) : DokkaConfiguration.PackageOptions
data class DokkaConfigurationImpl(
- override val moduleName: String,
- override val classpath: List<String>,
- override val sourceRoots: List<SourceRootImpl>,
- override val samples: List<String>,
- override val includes: List<String>,
- override val outputDir: String,
- override val format: String,
- override val includeNonPublic: Boolean,
- override val includeRootPackage: Boolean,
- override val reportUndocumented: Boolean,
- override val skipEmptyPackages: Boolean,
- override val skipDeprecated: Boolean,
- override val jdkVersion: Int,
- override val generateIndexPages: Boolean,
- override val sourceLinks: List<SourceLinkDefinitionImpl>,
- override val impliedPlatforms: List<String>,
- override val perPackageOptions: List<PackageOptionsImpl>,
- override val externalDocumentationLinks: List<ExternalDocumentationLinkImpl>,
- override val noStdlibLink: Boolean,
- override val cacheRoot: String?,
- override val suppressedFiles: List<String>,
- override val languageVersion: String?,
- override val apiVersion: String?
-) : DokkaConfiguration \ No newline at end of file
+ override val outputDir: String = "",
+ override val format: String = "html",
+ override val generateIndexPages: Boolean = false,
+ override val cacheRoot: String? = null,
+ override val impliedPlatforms: List<String> = listOf(),
+ override val passesConfigurations: List<DokkaConfiguration.PassConfiguration> = listOf()
+) : DokkaConfiguration
+
+class PassConfigurationImpl (
+ override val classpath: List<String> = listOf(),
+ override val moduleName: String = "",
+ override val sourceRoots: List<SourceRoot> = listOf(),
+ override val samples: List<String> = listOf(),
+ override val includes: List<String> = listOf(),
+ override val includeNonPublic: Boolean = false,
+ override val includeRootPackage: Boolean = false,
+ override val reportUndocumented: Boolean = false,
+ override val skipEmptyPackages: Boolean = false,
+ override val skipDeprecated: Boolean = false,
+ override val jdkVersion: Int = 6,
+ override val sourceLinks: List<SourceLinkDefinition> = listOf(),
+ override val perPackageOptions: List<DokkaConfiguration.PackageOptions> = listOf(),
+ externalDocumentationLinks: List<DokkaConfiguration.ExternalDocumentationLink> = listOf(),
+ override val languageVersion: String? = null,
+ override val apiVersion: String? = null,
+ override val noStdlibLink: Boolean = false,
+ override val noJdkLink: Boolean = false,
+ override val suppressedFiles: List<String> = listOf(),
+ override val collectInheritedExtensionsFromLibraries: Boolean = false,
+ override val analysisPlatform: Platform = Platform.DEFAULT,
+ override val targets: List<String> = listOf()
+): DokkaConfiguration.PassConfiguration {
+ private val defaultLinks = run {
+ val links = mutableListOf<DokkaConfiguration.ExternalDocumentationLink>()
+ if (!noJdkLink)
+ links += DokkaConfiguration.ExternalDocumentationLink.Builder("http://docs.oracle.com/javase/$jdkVersion/docs/api/").build()
+
+ if (!noStdlibLink)
+ links += DokkaConfiguration.ExternalDocumentationLink.Builder("https://kotlinlang.org/api/latest/jvm/stdlib/").build()
+ links
+ }
+ override val externalDocumentationLinks = defaultLinks + externalDocumentationLinks
+}
+
diff --git a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
index cf2b0514..1fe4d180 100644
--- a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
+++ b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
@@ -1,7 +1,9 @@
package org.jetbrains.dokka
import com.google.inject.Inject
+import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
+import com.intellij.psi.impl.JavaConstantExpressionEvaluator
import com.intellij.psi.util.InheritanceUtil
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
@@ -11,9 +13,9 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtModifierListOwner
-import java.io.File
fun getSignature(element: PsiElement?) = when(element) {
+ is PsiPackage -> element.qualifiedName
is PsiClass -> element.qualifiedName
is PsiField -> element.containingClass!!.qualifiedName + "$" + element.name
is PsiMethod ->
@@ -41,18 +43,24 @@ interface JavaDocumentationBuilder {
}
class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
- private val options: DocumentationOptions
+ private val passConfiguration: DokkaConfiguration.PassConfiguration
private val refGraph: NodeReferenceGraph
private val docParser: JavaDocumentationParser
- @Inject constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, logger: DokkaLogger) {
- this.options = options
+ @Inject constructor(
+ passConfiguration: DokkaConfiguration.PassConfiguration,
+ refGraph: NodeReferenceGraph,
+ logger: DokkaLogger,
+ signatureProvider: ElementSignatureProvider,
+ externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
+ ) {
+ this.passConfiguration = passConfiguration
this.refGraph = refGraph
- this.docParser = JavadocParser(refGraph, logger)
+ this.docParser = JavadocParser(refGraph, logger, signatureProvider, externalDocumentationLinkResolver)
}
- constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) {
- this.options = options
+ constructor(passConfiguration: DokkaConfiguration.PassConfiguration, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) {
+ this.passConfiguration = passConfiguration
this.refGraph = refGraph
this.docParser = docParser
}
@@ -61,7 +69,7 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
if (skipFile(file) || file.classes.all { skipElement(it) }) {
return
}
- val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap(), refGraph)
+ val packageNode = findOrCreatePackageNode(module, file.packageName, emptyMap(), refGraph)
appendClasses(packageNode, file.classes)
}
@@ -132,21 +140,23 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
}
}
- private fun skipFile(javaFile: PsiJavaFile): Boolean = options.effectivePackageOptions(javaFile.packageName).suppress
+ private fun skipFile(javaFile: PsiJavaFile): Boolean = passConfiguration.effectivePackageOptions(javaFile.packageName).suppress
private fun skipElement(element: Any) =
skipElementByVisibility(element) ||
hasSuppressDocTag(element) ||
skipElementBySuppressedFiles(element)
- private fun skipElementByVisibility(element: Any): Boolean = element is PsiModifierListOwner &&
- !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) &&
- (element.hasModifierProperty(PsiModifier.PRIVATE) ||
- element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
- element.isInternal())
+ private fun skipElementByVisibility(element: Any): Boolean =
+ element is PsiModifierListOwner &&
+ element !is PsiParameter &&
+ !(passConfiguration.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) &&
+ (element.hasModifierProperty(PsiModifier.PRIVATE) ||
+ element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
+ element.isInternal())
private fun skipElementBySuppressedFiles(element: Any): Boolean =
- element is PsiElement && File(element.containingFile.virtualFile.path).absoluteFile in options.suppressedFiles
+ element is PsiElement && element.containingFile.virtualFile.path in passConfiguration.suppressedFiles
private fun PsiElement.isInternal(): Boolean {
val ktElement = (this as? KtLightElement<*, *>)?.kotlinOrigin ?: return false
@@ -200,11 +210,28 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
fun PsiField.build(): DocumentationNode {
val node = nodeForElement(this, nodeKind())
node.appendType(type)
- node.appendModifiers(this)
+
+ node.appendConstantValueIfAny(this)
register(this, node)
return node
}
+ private fun DocumentationNode.appendConstantValueIfAny(field: PsiField) {
+ val modifierList = field.modifierList ?: return
+ val initializer = field.initializer ?: return
+ if (field.type is PsiPrimitiveType &&
+ modifierList.hasExplicitModifier(PsiModifier.FINAL) &&
+ modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
+ val value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false)
+ val text = when(value) {
+ is String ->
+ "\"" + StringUtil.escapeStringCharacters(value) + "\""
+ else -> value.toString()
+ }
+ append(DocumentationNode(text, Content.Empty, NodeKind.Value), RefKind.Detail)
+ }
+ }
+
private fun PsiField.nodeKind(): NodeKind = when {
this is PsiEnumConstant -> NodeKind.EnumItem
else -> NodeKind.Field
diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt
index c25f5813..9f9ea017 100644
--- a/core/src/main/kotlin/Java/JavadocParser.kt
+++ b/core/src/main/kotlin/Java/JavadocParser.kt
@@ -1,14 +1,18 @@
package org.jetbrains.dokka
import com.intellij.psi.*
-import com.intellij.psi.javadoc.PsiDocTag
-import com.intellij.psi.javadoc.PsiDocTagValue
-import com.intellij.psi.javadoc.PsiDocToken
-import com.intellij.psi.javadoc.PsiInlineDocTag
+import com.intellij.psi.impl.source.javadoc.CorePsiDocTagValueImpl
+import com.intellij.psi.impl.source.tree.JavaDocElementType
+import com.intellij.psi.javadoc.*
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.IncorrectOperationException
+import com.intellij.util.containers.isNullOrEmpty
+import org.jetbrains.kotlin.utils.keysToMap
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
import org.jsoup.nodes.TextNode
+import java.net.URI
data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) {
companion object {
@@ -20,70 +24,151 @@ interface JavaDocumentationParser {
fun parseDocumentation(element: PsiNamedElement): JavadocParseResult
}
-class JavadocParser(private val refGraph: NodeReferenceGraph,
- private val logger: DokkaLogger) : JavaDocumentationParser {
+class JavadocParser(
+ private val refGraph: NodeReferenceGraph,
+ private val logger: DokkaLogger,
+ private val signatureProvider: ElementSignatureProvider,
+ private val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
+) : JavaDocumentationParser {
+
+ private fun ContentSection.appendTypeElement(signature: String, selector: (DocumentationNode) -> DocumentationNode?) {
+ append(LazyContentBlock {
+ val node = refGraph.lookupOrWarn(signature, logger)?.let(selector)
+ if (node != null) {
+ it.append(NodeRenderContent(node, LanguageService.RenderMode.SUMMARY))
+ it.symbol(":")
+ it.text(" ")
+ }
+ })
+ }
+
override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult {
val docComment = (element as? PsiDocCommentOwner)?.docComment
if (docComment == null) return JavadocParseResult.Empty
val result = MutableContent()
var deprecatedContent: Content? = null
- val para = ContentParagraph()
- result.append(para)
- para.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() })
+ val firstParagraph = ContentParagraph()
+ firstParagraph.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }, element)
+ val paragraphs = firstParagraph.children.dropWhile { it !is ContentParagraph }
+ firstParagraph.children.removeAll(paragraphs)
+ if (!firstParagraph.isEmpty()) {
+ result.append(firstParagraph)
+ }
+ paragraphs.forEach {
+ result.append(it)
+ }
+
+ if (element is PsiMethod) {
+ val tagsByName = element.searchInheritedTags()
+ for ((tagName, tags) in tagsByName) {
+ for ((tag, context) in tags) {
+ val section = result.addSection(javadocSectionDisplayName(tagName), tag.getSubjectName())
+ val signature = signatureProvider.signature(element)
+ when (tagName) {
+ "param" -> {
+ section.appendTypeElement(signature) {
+ it.details.find { it.kind == NodeKind.Parameter }?.detailOrNull(NodeKind.Type)
+ }
+ }
+ "return" -> {
+ section.appendTypeElement(signature) { it.detailOrNull(NodeKind.Type) }
+ }
+ }
+ section.convertJavadocElements(tag.contentElements(), context)
+ }
+ }
+ }
+
docComment.tags.forEach { tag ->
- when(tag.name) {
+ when (tag.name) {
"see" -> result.convertSeeTag(tag)
"deprecated" -> {
- deprecatedContent = Content()
- deprecatedContent!!.convertJavadocElements(tag.contentElements())
+ deprecatedContent = Content().apply {
+ convertJavadocElements(tag.contentElements(), element)
+ }
}
+ in tagsToInherit -> {}
else -> {
val subjectName = tag.getSubjectName()
val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName)
- section.convertJavadocElements(tag.contentElements())
+ section.convertJavadocElements(tag.contentElements(), element)
}
}
}
return JavadocParseResult(result, deprecatedContent)
}
+ private val tagsToInherit = setOf("param", "return", "throws")
+
+ private data class TagWithContext(val tag: PsiDocTag, val context: PsiNamedElement)
+
+ private fun PsiMethod.searchInheritedTags(): Map<String, Collection<TagWithContext>> {
+
+ val output = tagsToInherit.keysToMap { mutableMapOf<String?, TagWithContext>() }
+
+ fun recursiveSearch(methods: Array<PsiMethod>) {
+ for (method in methods) {
+ recursiveSearch(method.findSuperMethods())
+ }
+ for (method in methods) {
+ for (tag in method.docComment?.tags.orEmpty()) {
+ if (tag.name in tagsToInherit) {
+ output[tag.name]!![tag.getSubjectName()] = TagWithContext(tag, method)
+ }
+ }
+ }
+ }
+
+ recursiveSearch(arrayOf(this))
+ return output.mapValues { it.value.values }
+ }
+
+
private fun PsiDocTag.contentElements(): Iterable<PsiElement> {
val tagValueElements = children
- .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME }
- .dropWhile { it is PsiWhiteSpace }
- .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS }
+ .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME }
+ .dropWhile { it is PsiWhiteSpace }
+ .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS }
return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements
}
- private fun ContentBlock.convertJavadocElements(elements: Iterable<PsiElement>) {
+ private fun ContentBlock.convertJavadocElements(elements: Iterable<PsiElement>, element: PsiNamedElement) {
+ val doc = Jsoup.parse(expandAllForElements(elements, element))
+ doc.body().childNodes().forEach {
+ convertHtmlNode(it)?.let { append(it) }
+ }
+ }
+
+ private fun expandAllForElements(elements: Iterable<PsiElement>, element: PsiNamedElement): String {
val htmlBuilder = StringBuilder()
elements.forEach {
if (it is PsiInlineDocTag) {
- htmlBuilder.append(convertInlineDocTag(it))
+ htmlBuilder.append(convertInlineDocTag(it, element))
} else {
htmlBuilder.append(it.text)
}
}
- val doc = Jsoup.parse(htmlBuilder.toString().trim())
- doc.body().childNodes().forEach {
- convertHtmlNode(it)
- }
+ return htmlBuilder.toString().trim()
}
- private fun ContentBlock.convertHtmlNode(node: Node) {
+ private fun convertHtmlNode(node: Node): ContentNode? {
if (node is TextNode) {
- append(ContentText(node.text()))
+ return ContentText(node.text())
} else if (node is Element) {
val childBlock = createBlock(node)
node.childNodes().forEach {
- childBlock.convertHtmlNode(it)
+ val child = convertHtmlNode(it)
+ if (child != null) {
+ childBlock.append(child)
+ }
}
- append(childBlock)
+ return (childBlock)
}
+ return null
}
- private fun createBlock(element: Element): ContentBlock = when(element.tagName()) {
+ private fun createBlock(element: Element): ContentBlock = when (element.tagName()) {
"p" -> ContentParagraph()
"b", "strong" -> ContentStrong()
"i", "em" -> ContentEmphasis()
@@ -99,45 +184,73 @@ class JavadocParser(private val refGraph: NodeReferenceGraph,
}
private fun createLink(element: Element): ContentBlock {
- val docref = element.attr("docref")
- if (docref != null) {
- return ContentNodeLazyLink(docref, { -> refGraph.lookupOrWarn(docref, logger)})
- }
- val href = element.attr("href")
- if (href != null) {
- return ContentExternalLink(href)
- } else {
- return ContentBlock()
+ return when {
+ element.hasAttr("docref") -> {
+ val docref = element.attr("docref")
+ ContentNodeLazyLink(docref, { -> refGraph.lookupOrWarn(docref, logger) })
+ }
+ element.hasAttr("href") -> {
+ val href = element.attr("href")
+
+ val uri = try {
+ URI(href)
+ } catch (_: Exception) {
+ null
+ }
+
+ if (uri?.isAbsolute == false) {
+ ContentLocalLink(href)
+ } else {
+ ContentExternalLink(href)
+ }
+ }
+ element.hasAttr("name") -> {
+ ContentBookmark(element.attr("name"))
+ }
+ else -> ContentBlock()
}
}
private fun MutableContent.convertSeeTag(tag: PsiDocTag) {
- val linkElement = tag.linkElement()
- if (linkElement == null) {
- return
- }
+ val linkElement = tag.linkElement() ?: return
val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null)
- val linkSignature = resolveLink(linkElement)
+
+ val valueElement = tag.referenceElement()
+ val externalLink = resolveExternalLink(valueElement)
val text = ContentText(linkElement.text)
- if (linkSignature != null) {
- val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookupOrWarn(linkSignature, logger)})
- linkNode.append(text)
- seeSection.append(linkNode)
- } else {
- seeSection.append(text)
+
+ val linkSignature by lazy { resolveInternalLink(valueElement) }
+ val node = when {
+ externalLink != null -> {
+ val linkNode = ContentExternalLink(externalLink)
+ linkNode.append(text)
+ linkNode
+ }
+ linkSignature != null -> {
+ val linkNode =
+ ContentNodeLazyLink(
+ (tag.valueElement ?: linkElement).text,
+ { -> refGraph.lookupOrWarn(linkSignature, logger) }
+ )
+ linkNode.append(text)
+ linkNode
+ }
+ else -> text
}
+ seeSection.append(node)
}
- private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) {
+ private fun convertInlineDocTag(tag: PsiInlineDocTag, element: PsiNamedElement) = when (tag.name) {
"link", "linkplain" -> {
- val valueElement = tag.linkElement()
- val linkSignature = resolveLink(valueElement)
- if (linkSignature != null) {
+ val valueElement = tag.referenceElement()
+ val externalLink = resolveExternalLink(valueElement)
+ val linkSignature by lazy { resolveInternalLink(valueElement) }
+ if (externalLink != null || linkSignature != null) {
val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text
- val link = "<a docref=\"$linkSignature\">${labelText.htmlEscape()}</a>"
+ val linkTarget = if (externalLink != null) "href=\"$externalLink\"" else "docref=\"$linkSignature\""
+ val link = "<a $linkTarget>${labelText.htmlEscape()}</a>"
if (tag.name == "link") "<code>$link</code>" else link
- }
- else if (valueElement != null) {
+ } else if (valueElement != null) {
valueElement.text
} else {
""
@@ -149,16 +262,45 @@ class JavadocParser(private val refGraph: NodeReferenceGraph,
val escaped = text.toString().trimStart().htmlEscape()
if (tag.name == "code") "<code>$escaped</code>" else escaped
}
+ "inheritDoc" -> {
+ val result = (element as? PsiMethod)?.let {
+ // @{inheritDoc} is only allowed on functions
+ val parent = tag.parent
+ when (parent) {
+ is PsiDocComment -> element.findSuperDocCommentOrWarn()
+ is PsiDocTag -> element.findSuperDocTagOrWarn(parent)
+ else -> null
+ }
+ }
+ result ?: tag.text
+ }
else -> tag.text
}
+ private fun PsiDocTag.referenceElement(): PsiElement? =
+ linkElement()?.let {
+ if (it.node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) {
+ PsiTreeUtil.findChildOfType(it, PsiJavaCodeReferenceElement::class.java)
+ } else {
+ it
+ }
+ }
+
private fun PsiDocTag.linkElement(): PsiElement? =
- valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+ valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+
+ private fun resolveExternalLink(valueElement: PsiElement?): String? {
+ val target = valueElement?.reference?.resolve()
+ if (target != null) {
+ return externalDocumentationLinkResolver.buildExternalDocumentationLink(target)
+ }
+ return null
+ }
- private fun resolveLink(valueElement: PsiElement?): String? {
+ private fun resolveInternalLink(valueElement: PsiElement?): String? {
val target = valueElement?.reference?.resolve()
if (target != null) {
- return getSignature(target)
+ return signatureProvider.signature(target)
}
return null
}
@@ -169,4 +311,88 @@ class JavadocParser(private val refGraph: NodeReferenceGraph,
}
return null
}
+
+ private fun PsiMethod.findSuperDocCommentOrWarn(): String {
+ val method = findFirstSuperMethodWithDocumentation(this)
+ if (method != null) {
+ val descriptionElements = method.docComment?.descriptionElements?.dropWhile {
+ it.text.trim().isEmpty()
+ } ?: return ""
+
+ return expandAllForElements(descriptionElements, method)
+ }
+ logger.warn("No docs found on supertype with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}")
+ return ""
+ }
+
+
+ private fun PsiMethod.findSuperDocTagOrWarn(elementToExpand: PsiDocTag): String {
+ val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, this)
+
+ if (result != null) {
+ val (method, tag) = result
+
+ val contentElements = tag.contentElements().dropWhile { it.text.trim().isEmpty() }
+
+ val expandedString = expandAllForElements(contentElements, method)
+
+ return expandedString
+ }
+ logger.warn("No docs found on supertype for @${elementToExpand.name} ${elementToExpand.getSubjectName()} with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}")
+ return ""
+ }
+
+ private fun findFirstSuperMethodWithDocumentation(current: PsiMethod): PsiMethod? {
+ val superMethods = current.findSuperMethods()
+ for (method in superMethods) {
+ val docs = method.docComment?.descriptionElements?.dropWhile { it.text.trim().isEmpty() }
+ if (!docs.isNullOrEmpty()) {
+ return method
+ }
+ }
+ for (method in superMethods) {
+ val result = findFirstSuperMethodWithDocumentation(method)
+ if (result != null) {
+ return result
+ }
+ }
+
+ return null
+ }
+
+ private fun findFirstSuperMethodWithDocumentationforTag(elementToExpand: PsiDocTag, current: PsiMethod): Pair<PsiMethod, PsiDocTag>? {
+ val superMethods = current.findSuperMethods()
+ val mappedFilteredTags = superMethods.map {
+ it to it.docComment?.tags?.filter { it.name == elementToExpand.name }
+ }
+
+ for ((method, tags) in mappedFilteredTags) {
+ tags ?: continue
+ for (tag in tags) {
+ val (tagSubject, elementSubject) = when (tag.name) {
+ "throws" -> {
+ // match class names only for throws, ignore possibly fully qualified path
+ // TODO: Always match exactly here
+ tag.getSubjectName()?.split(".")?.last() to elementToExpand.getSubjectName()?.split(".")?.last()
+ }
+ else -> {
+ tag.getSubjectName() to elementToExpand.getSubjectName()
+ }
+ }
+
+ if (tagSubject == elementSubject) {
+ return method to tag
+ }
+ }
+ }
+
+ for (method in superMethods) {
+ val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, method)
+ if (result != null) {
+ return result
+ }
+ }
+ return null
+ }
+
}
diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
index ffef399d..c3a84e57 100644
--- a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
@@ -1,21 +1,18 @@
package org.jetbrains.dokka
import com.google.inject.Inject
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
-import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi
-import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
class DeclarationLinkResolver
@Inject constructor(val resolutionFacade: DokkaResolutionFacade,
val refGraph: NodeReferenceGraph,
val logger: DokkaLogger,
- val options: DocumentationOptions,
+ val passConfiguration: DokkaConfiguration.PassConfiguration,
val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver,
- val descriptorSignatureProvider: DescriptorSignatureProvider) {
+ val elementSignatureProvider: ElementSignatureProvider) {
fun tryResolveContentLink(fromDescriptor: DeclarationDescriptor, href: String): ContentBlock? {
@@ -34,14 +31,14 @@ class DeclarationLinkResolver
if (externalHref != null) {
return ContentExternalLink(externalHref)
}
- val signature = descriptorSignatureProvider.signature(symbol)
+ val signature = elementSignatureProvider.signature(symbol)
val referencedAt = fromDescriptor.signatureWithSourceLocation()
return ContentNodeLazyLink(href, { ->
val target = refGraph.lookup(signature)
if (target == null) {
- logger.warn("Can't find node by signature $signature, referenced at $referencedAt")
+ logger.warn("Can't find node by signature `$signature`, referenced at $referencedAt")
}
target
})
@@ -66,7 +63,7 @@ class DeclarationLinkResolver
if (symbol is CallableMemberDescriptor && symbol.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
return symbol.overriddenDescriptors.firstOrNull()
}
- if (symbol is TypeAliasDescriptor && !symbol.isDocumented(options)) {
+ if (symbol is TypeAliasDescriptor && !symbol.isDocumented(passConfiguration)) {
return symbol.classDescriptor
}
return symbol
diff --git a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
index eb8c12d0..d0650d45 100644
--- a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
+++ b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
@@ -8,14 +8,17 @@ import org.intellij.markdown.parser.LinkMap
import org.jetbrains.dokka.*
import org.jetbrains.dokka.Samples.SampleProcessingService
import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.kdoc.findKDoc
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.constants.StringValue
@@ -25,12 +28,15 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
class DescriptorDocumentationParser
- @Inject constructor(val options: DocumentationOptions,
+ @Inject constructor(val options: DokkaConfiguration.PassConfiguration,
val logger: DokkaLogger,
val linkResolver: DeclarationLinkResolver,
val resolutionFacade: DokkaResolutionFacade,
val refGraph: NodeReferenceGraph,
- val sampleService: SampleProcessingService)
+ val sampleService: SampleProcessingService,
+ val signatureProvider: KotlinElementSignatureProvider,
+ val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
+)
{
fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content =
parseDocumentationAndDetails(descriptor, inline).first
@@ -49,6 +55,13 @@ class DescriptorDocumentationParser
}
return Content.Empty to { node -> }
}
+
+ val contextDescriptor =
+ (PsiTreeUtil.getParentOfType(kdoc, KDoc::class.java)?.context as? KtDeclaration)
+ ?.takeIf { it != descriptor.original.sourcePsi() }
+ ?.resolveToDescriptorIfAny()
+ ?: descriptor
+
var kdocText = kdoc.getContent()
// workaround for code fence parsing problem in IJ markdown parser
if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) {
@@ -56,20 +69,20 @@ class DescriptorDocumentationParser
}
val tree = parseMarkdown(kdocText)
val linkMap = LinkMap.buildLinkMap(tree.node, kdocText)
- val content = buildContent(tree, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(descriptor, href) }), inline)
+ val content = buildContent(tree, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }), inline)
if (kdoc is KDocSection) {
val tags = kdoc.getTags()
tags.forEach {
when (it.knownTag) {
KDocKnownTag.SAMPLE ->
- content.append(sampleService.resolveSample(descriptor, it.getSubjectName(), it))
+ content.append(sampleService.resolveSample(contextDescriptor, it.getSubjectName(), it))
KDocKnownTag.SEE ->
- content.addTagToSeeAlso(descriptor, it)
+ content.addTagToSeeAlso(contextDescriptor, it)
else -> {
val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
val sectionContent = it.getContent()
val markdownNode = parseMarkdown(sectionContent)
- buildInlineContentTo(markdownNode, section, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(descriptor, href) }))
+ buildInlineContentTo(markdownNode, section, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }))
}
}
}
@@ -81,7 +94,7 @@ class DescriptorDocumentationParser
val suppressAnnotation = annotations.findAnnotation(FqName(Suppress::class.qualifiedName!!))
return if (suppressAnnotation != null) {
@Suppress("UNCHECKED_CAST")
- (suppressAnnotation.argumentValue("names") as List<StringValue>).any { it.value == "NOT_DOCUMENTED" }
+ (suppressAnnotation.argumentValue("names")?.value as List<StringValue>).any { it.value == "NOT_DOCUMENTED" }
} else containingDeclaration?.isSuppressWarning() ?: false
}
@@ -119,7 +132,12 @@ class DescriptorDocumentationParser
fun parseJavadoc(descriptor: DeclarationDescriptor): Pair<Content, (DocumentationNode) -> Unit> {
val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi
if (psi is PsiDocCommentOwner) {
- val parseResult = JavadocParser(refGraph, logger).parseDocumentation(psi as PsiNamedElement)
+ val parseResult = JavadocParser(
+ refGraph,
+ logger,
+ signatureProvider,
+ externalDocumentationLinkResolver
+ ).parseDocumentation(psi as PsiNamedElement)
return parseResult.content to { node ->
parseResult.deprecatedContent?.let {
val deprecationNode = DocumentationNode("", it, NodeKind.Modifier)
diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
index 61bf50d6..e5bc32ab 100644
--- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -2,8 +2,9 @@ package org.jetbrains.dokka
import com.google.inject.Inject
import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.PsiField
import com.intellij.psi.PsiJavaFile
-import org.jetbrains.dokka.DokkaConfiguration.*
+import org.jetbrains.dokka.DokkaConfiguration.PassConfiguration
import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -11,74 +12,31 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
import org.jetbrains.kotlin.idea.kdoc.findKDoc
+import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType
+import org.jetbrains.kotlin.idea.util.makeNotNullable
+import org.jetbrains.kotlin.idea.util.toFuzzyType
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
+import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.psi.KtVariableDeclaration
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors
-import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
+import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.*
-import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.supertypes
-import java.io.File
-import java.nio.file.Path
-import java.nio.file.Paths
+import org.jetbrains.kotlin.util.supertypesWithAny
import com.google.inject.name.Named as GuiceNamed
-class DocumentationOptions(val outputDir: String,
- val outputFormat: String,
- includeNonPublic: Boolean = false,
- val includeRootPackage: Boolean = false,
- reportUndocumented: Boolean = true,
- val skipEmptyPackages: Boolean = true,
- skipDeprecated: Boolean = false,
- jdkVersion: Int = 6,
- val generateIndexPages: Boolean = true,
- val sourceLinks: List<SourceLinkDefinition> = emptyList(),
- val impliedPlatforms: List<String> = emptyList(),
- // Sorted by pattern length
- perPackageOptions: List<PackageOptions> = emptyList(),
- externalDocumentationLinks: List<ExternalDocumentationLink> = emptyList(),
- noStdlibLink: Boolean,
- val languageVersion: String?,
- val apiVersion: String?,
- cacheRoot: String? = null,
- val suppressedFiles: List<File> = emptyList()) {
- init {
- if (perPackageOptions.any { it.prefix == "" })
- throw IllegalArgumentException("Please do not register packageOptions with all match pattern, use global settings instead")
- }
-
- val perPackageOptions = perPackageOptions.sortedByDescending { it.prefix.length }
- val rootPackageOptions = PackageOptionsImpl("", includeNonPublic, reportUndocumented, skipDeprecated)
-
- fun effectivePackageOptions(pack: String): PackageOptions = perPackageOptions.firstOrNull { pack == it.prefix || pack.startsWith(it.prefix + ".") } ?: rootPackageOptions
- fun effectivePackageOptions(pack: FqName): PackageOptions = effectivePackageOptions(pack.asString())
-
- val defaultLinks = run {
- val links = mutableListOf(ExternalDocumentationLink.Builder("http://docs.oracle.com/javase/$jdkVersion/docs/api/").build())
- if (!noStdlibLink)
- links += ExternalDocumentationLink.Builder("https://kotlinlang.org/api/latest/jvm/stdlib/").build()
- links
- }
-
- val externalDocumentationLinks = defaultLinks + externalDocumentationLinks
-
- val cacheRoot: Path? = when {
- cacheRoot == "default" -> Paths.get(System.getProperty("user.home"), ".cache", "dokka")
- cacheRoot != null -> Paths.get(cacheRoot)
- else -> null
- }
-}
-
private fun isExtensionForExternalClass(extensionFunctionDescriptor: DeclarationDescriptor,
extensionReceiverDescriptor: DeclarationDescriptor,
allFqNames: Collection<FqName>): Boolean {
@@ -101,10 +59,14 @@ interface DefaultPlatformsProvider {
fun getDefaultPlatforms(descriptor: DeclarationDescriptor): List<String>
}
+val ignoredSupertypes = setOf(
+ "kotlin.Annotation", "kotlin.Enum", "kotlin.Any"
+)
+
class DocumentationBuilder
@Inject constructor(val resolutionFacade: DokkaResolutionFacade,
val descriptorDocumentationParser: DescriptorDocumentationParser,
- val options: DocumentationOptions,
+ val passConfiguration: DokkaConfiguration.PassConfiguration,
val refGraph: NodeReferenceGraph,
val platformNodeRegistry: PlatformNodeRegistry,
val logger: DokkaLogger,
@@ -132,8 +94,20 @@ class DocumentationBuilder
refGraph.register(descriptor.signature(), node)
}
- fun <T> nodeForDescriptor(descriptor: T, kind: NodeKind): DocumentationNode where T : DeclarationDescriptor, T : Named {
- val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor, kind == NodeKind.Parameter)
+ fun <T> nodeForDescriptor(
+ descriptor: T,
+ kind: NodeKind,
+ external: Boolean = false
+ ): DocumentationNode where T : DeclarationDescriptor, T : Named {
+ val (doc, callback) =
+ if (external) {
+ Content.Empty to { node -> }
+ } else {
+ descriptorDocumentationParser.parseDocumentationAndDetails(
+ descriptor,
+ kind == NodeKind.Parameter
+ )
+ }
val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor)
node.appendSignature(descriptor)
callback(node)
@@ -167,27 +141,20 @@ class DocumentationBuilder
appendTextNode(modifier, NodeKind.Modifier)
}
- fun DocumentationNode.appendSupertype(descriptor: ClassDescriptor, superType: KotlinType) {
+ fun DocumentationNode.appendSupertype(descriptor: ClassDescriptor, superType: KotlinType, backref: Boolean) {
val unwrappedType = superType.unwrap()
if (unwrappedType is AbbreviatedType) {
- appendSupertype(descriptor, unwrappedType.abbreviation)
- } else if (!ignoreSupertype(unwrappedType)) {
+ appendSupertype(descriptor, unwrappedType.abbreviation, backref)
+ } else {
appendType(unwrappedType, NodeKind.Supertype)
val superclass = unwrappedType.constructor.declarationDescriptor
- link(superclass, descriptor, RefKind.Inheritor)
+ if (backref) {
+ link(superclass, descriptor, RefKind.Inheritor)
+ }
link(descriptor, superclass, RefKind.Superclass)
}
}
- private fun ignoreSupertype(superType: KotlinType): Boolean {
- val superClass = superType.constructor.declarationDescriptor as? ClassDescriptor
- if (superClass != null) {
- val fqName = DescriptorUtils.getFqNameSafe(superClass).asString()
- return fqName == "kotlin.Annotation" || fqName == "kotlin.Enum" || fqName == "kotlin.Any"
- }
- return false
- }
-
fun DocumentationNode.appendProjection(projection: TypeProjection, kind: NodeKind = NodeKind.Type) {
if (projection.isStarProjection) {
appendTextNode("*", NodeKind.Type)
@@ -225,19 +192,39 @@ class DocumentationBuilder
if (prefix != "") {
node.appendTextNode(prefix, NodeKind.Modifier)
}
- if (kotlinType.isMarkedNullable) {
+ if (kotlinType.isNullabilityFlexible()) {
+ node.appendTextNode("!", NodeKind.NullabilityModifier)
+ } else if (kotlinType.isMarkedNullable) {
node.appendTextNode("?", NodeKind.NullabilityModifier)
}
if (classifierDescriptor != null) {
- val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor)
+ val externalLink =
+ linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor)
if (externalLink != null) {
- node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ if (classifierDescriptor !is TypeParameterDescriptor) {
+ val targetNode =
+ refGraph.lookup(classifierDescriptor.signature()) ?: classifierDescriptor.build(true)
+ node.append(targetNode, RefKind.ExternalType)
+ node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ }
} else {
- link(node, classifierDescriptor,
- if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link)
+ link(
+ node, classifierDescriptor,
+ if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link
+ )
+ }
+ if (classifierDescriptor !is TypeParameterDescriptor) {
+ node.append(
+ DocumentationNode(
+ classifierDescriptor.fqNameUnsafe.asString(),
+ Content.Empty,
+ NodeKind.QualifiedName
+ ), RefKind.Detail
+ )
}
}
+
append(node, RefKind.Detail)
node.appendAnnotations(kotlinType)
for (typeArgument in kotlinType.arguments) {
@@ -271,6 +258,17 @@ class DocumentationBuilder
}
}
+ fun DocumentationNode.appendExternalLink(externalLink: String) {
+ append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ }
+
+ fun DocumentationNode.appendExternalLink(descriptor: DeclarationDescriptor) {
+ val target = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(descriptor)
+ if (target != null) {
+ appendExternalLink(target)
+ }
+ }
+
fun DocumentationNode.appendSinceKotlin(annotation: DocumentationNode) {
val kotlinVersion = annotation
.detail(NodeKind.Parameter)
@@ -300,7 +298,7 @@ class DocumentationBuilder
fun DocumentationNode.isSinceKotlin() = name == "SinceKotlin" && kind == NodeKind.Annotation
fun DocumentationNode.appendSourceLink(sourceElement: SourceElement) {
- appendSourceLink(sourceElement.getPsi(), options.sourceLinks)
+ appendSourceLink(sourceElement.getPsi(), passConfiguration.sourceLinks)
}
fun DocumentationNode.appendSignature(descriptor: DeclarationDescriptor) {
@@ -308,7 +306,7 @@ class DocumentationBuilder
}
fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: RefKind): DocumentationNode? {
- if (!descriptor.isGenerated() && descriptor.isDocumented(options)) {
+ if (!descriptor.isGenerated() && descriptor.isDocumented(passConfiguration)) {
val node = descriptor.build()
append(node, kind)
return node
@@ -335,7 +333,7 @@ class DocumentationBuilder
fun DocumentationNode.appendOrUpdateMember(descriptor: DeclarationDescriptor) {
- if (descriptor.isGenerated() || !descriptor.isDocumented(options)) return
+ if (descriptor.isGenerated() || !descriptor.isDocumented(passConfiguration)) return
val existingNode = refGraph.lookup(descriptor.signature())
if (existingNode != null) {
@@ -407,25 +405,54 @@ class DocumentationBuilder
val allFqNames = fragments.map { it.fqName }.distinct()
for (packageName in allFqNames) {
- if (packageName.isRoot && !options.includeRootPackage) continue
+ if (packageName.isRoot && !passConfiguration.includeRootPackage) continue
val declarations = fragments.filter { it.fqName == packageName }.flatMap { it.getMemberScope().getContributedDescriptors() }
- if (options.skipEmptyPackages && declarations.none { it.isDocumented(options) }) continue
+ if (passConfiguration.skipEmptyPackages && declarations.none { it.isDocumented(passConfiguration) }) continue
logger.info(" package $packageName: ${declarations.count()} declarations")
- val packageNode = findOrCreatePackageNode(packageName.asString(), packageContent, this@DocumentationBuilder.refGraph)
+ val packageNode = findOrCreatePackageNode(this, packageName.asString(), packageContent, this@DocumentationBuilder.refGraph)
packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode,
declarations, allFqNames)
}
- propagateExtensionFunctionsToSubclasses(fragments)
}
- private fun propagateExtensionFunctionsToSubclasses(fragments: Collection<PackageFragmentDescriptor>) {
- val allDescriptors = fragments.flatMap { it.getMemberScope().getContributedDescriptors() }
- val allClasses = allDescriptors.filterIsInstance<ClassDescriptor>()
- val classHierarchy = buildClassHierarchy(allClasses)
+ fun propagateExtensionFunctionsToSubclasses(
+ fragments: Collection<PackageFragmentDescriptor>,
+ resolutionFacade: DokkaResolutionFacade
+ ) {
+
+ val moduleDescriptor = resolutionFacade.moduleDescriptor
+
+ // Wide-collect all view descriptors
+ val allPackageViewDescriptors = generateSequence(listOf(moduleDescriptor.getPackage(FqName.ROOT))) { packages ->
+ packages
+ .flatMap { pkg ->
+ moduleDescriptor.getSubPackagesOf(pkg.fqName) { true }
+ }.map { fqName ->
+ moduleDescriptor.getPackage(fqName)
+ }.takeUnless { it.isEmpty() }
+ }.flatten()
+
+ val allDescriptors =
+ if (passConfiguration.collectInheritedExtensionsFromLibraries) {
+ allPackageViewDescriptors.map { it.memberScope }
+ } else {
+ fragments.asSequence().map { it.getMemberScope() }
+ }.flatMap {
+ it.getDescriptorsFiltered(
+ DescriptorKindFilter.CALLABLES
+ ).asSequence()
+ }
+
+
+ val documentingDescriptors = fragments.flatMap { it.getMemberScope().getContributedDescriptors() }
+ val documentingClasses = documentingDescriptors.filterIsInstance<ClassDescriptor>()
- val allExtensionFunctions = allDescriptors
+ val classHierarchy = buildClassHierarchy(documentingClasses)
+
+ val allExtensionFunctions =
+ allDescriptors
.filterIsInstance<CallableMemberDescriptor>()
.filter { it.extensionReceiverParameter != null }
val extensionFunctionsByName = allExtensionFunctions.groupBy { it.name }
@@ -433,15 +460,31 @@ class DocumentationBuilder
for (extensionFunction in allExtensionFunctions) {
if (extensionFunction.dispatchReceiverParameter != null) continue
val possiblyShadowingFunctions = extensionFunctionsByName[extensionFunction.name]
- ?.filter { fn -> fn.canShadow(extensionFunction) }
+ ?.filter { fn -> fn.canShadow(extensionFunction) }
?: emptyList()
if (extensionFunction.extensionReceiverParameter?.type?.isDynamic() == true) continue
- val classDescriptor = extensionFunction.getExtensionClassDescriptor() ?: continue
- val subclasses = classHierarchy[classDescriptor] ?: continue
- subclasses.forEach { subclass ->
+ val subclasses =
+ classHierarchy.filter { (key) -> key.isExtensionApplicable(extensionFunction) }
+ if (subclasses.isEmpty()) continue
+ subclasses.values.flatten().forEach { subclass ->
if (subclass.isExtensionApplicable(extensionFunction) &&
- possiblyShadowingFunctions.none { subclass.isExtensionApplicable(it) }) {
+ possiblyShadowingFunctions.none { subclass.isExtensionApplicable(it) }) {
+
+ val hasExternalLink =
+ linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(
+ extensionFunction
+ ) != null
+ if (hasExternalLink) {
+ val containerDesc =
+ extensionFunction.containingDeclaration as? PackageFragmentDescriptor
+ if (containerDesc != null) {
+ val container = refGraph.lookup(containerDesc.signature())
+ ?: containerDesc.buildExternal()
+ container.append(extensionFunction.buildExternal(), RefKind.Member)
+ }
+ }
+
refGraph.link(subclass.signature(), extensionFunction.signature(), RefKind.Extension)
}
}
@@ -449,12 +492,9 @@ class DocumentationBuilder
}
private fun ClassDescriptor.isExtensionApplicable(extensionFunction: CallableMemberDescriptor): Boolean {
- val receiverType = extensionFunction.extensionReceiverParameter!!.type
- if (receiverType.arguments.any { it.type.constructor.declarationDescriptor is TypeParameterDescriptor }) {
- val receiverClass = receiverType.constructor.declarationDescriptor
- return receiverClass is ClassDescriptor && DescriptorUtils.isSubclass(this, receiverClass)
- }
- return defaultType.isSubtypeOf(receiverType)
+ val receiverType = extensionFunction.fuzzyExtensionReceiverType()?.makeNotNullable()
+ val classType = defaultType.toFuzzyType(declaredTypeParameters)
+ return receiverType != null && classType.checkIsSubtypeOf(receiverType) != null
}
private fun buildClassHierarchy(classes: List<ClassDescriptor>): Map<ClassDescriptor, List<ClassDescriptor>> {
@@ -493,34 +533,60 @@ class DocumentationBuilder
}
fun DeclarationDescriptor.build(): DocumentationNode = when (this) {
- is ClassDescriptor -> build()
+ is ClassifierDescriptor -> build()
is ConstructorDescriptor -> build()
is PropertyDescriptor -> build()
is FunctionDescriptor -> build()
- is TypeParameterDescriptor -> build()
is ValueParameterDescriptor -> build()
is ReceiverParameterDescriptor -> build()
- is TypeAliasDescriptor -> build()
else -> throw IllegalStateException("Descriptor $this is not known")
}
- fun TypeAliasDescriptor.build(): DocumentationNode {
+ fun PackageFragmentDescriptor.buildExternal(): DocumentationNode {
+ val node = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.Package)
+
+ val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(this)
+ if (externalLink != null) {
+ node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ }
+ register(this, node)
+ return node
+ }
+
+ fun CallableDescriptor.buildExternal(): DocumentationNode = when(this) {
+ is FunctionDescriptor -> build(true)
+ is PropertyDescriptor -> build(true)
+ else -> throw IllegalStateException("Descriptor $this is not known")
+ }
+
+
+ fun ClassifierDescriptor.build(external: Boolean = false): DocumentationNode = when (this) {
+ is ClassDescriptor -> build(external)
+ is TypeAliasDescriptor -> build(external)
+ is TypeParameterDescriptor -> build()
+ else -> throw IllegalStateException("Descriptor $this is not known")
+ }
+
+ fun TypeAliasDescriptor.build(external: Boolean = false): DocumentationNode {
val node = nodeForDescriptor(this, NodeKind.TypeAlias)
- node.appendAnnotations(this)
+ if (!external) {
+ node.appendAnnotations(this)
+ }
node.appendModifiers(this)
node.appendInPageChildren(typeConstructor.parameters, RefKind.Detail)
node.appendType(underlyingType, NodeKind.TypeAliasUnderlyingType)
- node.appendSourceLink(source)
- node.appendDefaultPlatforms(this)
-
+ if (!external) {
+ node.appendSourceLink(source)
+ node.appendDefaultPlatforms(this)
+ }
register(this, node)
return node
}
- fun ClassDescriptor.build(): DocumentationNode {
+ fun ClassDescriptor.build(external: Boolean = false): DocumentationNode {
val kind = when {
kind == ClassKind.OBJECT -> NodeKind.Object
kind == ClassKind.INTERFACE -> NodeKind.Interface
@@ -530,21 +596,25 @@ class DocumentationBuilder
isSubclassOfThrowable() -> NodeKind.Exception
else -> NodeKind.Class
}
- val node = nodeForDescriptor(this, kind)
- typeConstructor.supertypes.forEach {
- node.appendSupertype(this, it)
+ val node = nodeForDescriptor(this, kind, external)
+ register(this, node)
+ supertypesWithAnyPrecise().forEach {
+ node.appendSupertype(this, it, !external)
}
if (getKind() != ClassKind.OBJECT && getKind() != ClassKind.ENUM_ENTRY) {
node.appendInPageChildren(typeConstructor.parameters, RefKind.Detail)
}
- for ((descriptor, inheritedLinkKind, extraModifier) in collectMembersToDocument()) {
- node.appendClassMember(descriptor, inheritedLinkKind, extraModifier)
+ if (!external) {
+ for ((descriptor, inheritedLinkKind, extraModifier) in collectMembersToDocument()) {
+ node.appendClassMember(descriptor, inheritedLinkKind, extraModifier)
+ }
+ node.appendAnnotations(this)
}
- node.appendAnnotations(this)
node.appendModifiers(this)
- node.appendSourceLink(source)
- node.appendDefaultPlatforms(this)
- register(this, node)
+ if (!external) {
+ node.appendSourceLink(source)
+ node.appendDefaultPlatforms(this)
+ }
return node
}
@@ -570,7 +640,7 @@ class DocumentationBuilder
.mapTo(result) { ClassMember(it, extraModifier = "static") }
val companionObjectDescriptor = companionObjectDescriptor
- if (companionObjectDescriptor != null && companionObjectDescriptor.isDocumented(options)) {
+ if (companionObjectDescriptor != null && companionObjectDescriptor.isDocumented(passConfiguration)) {
val descriptors = companionObjectDescriptor.defaultType.memberScope.getContributedDescriptors()
val descriptorsToDocument = descriptors.filter { it !is CallableDescriptor || !it.isInheritedFromAny() }
descriptorsToDocument.mapTo(result) {
@@ -611,12 +681,12 @@ class DocumentationBuilder
return (receiver?.type?.constructor?.declarationDescriptor as? ClassDescriptor)?.isCompanionObject ?: false
}
- fun FunctionDescriptor.build(): DocumentationNode {
+ fun FunctionDescriptor.build(external: Boolean = false): DocumentationNode {
if (ErrorUtils.containsErrorType(this)) {
logger.warn("Found an unresolved type in ${signatureWithSourceLocation()}")
}
- val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectFunction else NodeKind.Function)
+ val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectFunction else NodeKind.Function, external)
node.appendInPageChildren(typeParameters, RefKind.Detail)
extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) }
@@ -624,8 +694,12 @@ class DocumentationBuilder
node.appendType(returnType)
node.appendAnnotations(this)
node.appendModifiers(this)
- node.appendSourceLink(source)
- node.appendDefaultPlatforms(this)
+ if (!external) {
+ node.appendSourceLink(source)
+ node.appendDefaultPlatforms(this)
+ } else {
+ node.appendExternalLink(this)
+ }
overriddenDescriptors.forEach {
addOverrideLink(it, this)
@@ -646,32 +720,53 @@ class DocumentationBuilder
}
}
- fun PropertyDescriptor.build(): DocumentationNode {
- val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectProperty else NodeKind.Property)
+ fun PropertyDescriptor.build(external: Boolean = false): DocumentationNode {
+ val node = nodeForDescriptor(
+ this,
+ if (inCompanionObject()) NodeKind.CompanionObjectProperty else NodeKind.Property,
+ external
+ )
node.appendInPageChildren(typeParameters, RefKind.Detail)
extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) }
node.appendType(returnType)
node.appendAnnotations(this)
node.appendModifiers(this)
- node.appendSourceLink(source)
- if (isVar) {
- node.appendTextNode("var", NodeKind.Modifier)
- }
- getter?.let {
- if (!it.isDefault) {
- node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter")
+ if (!external) {
+ node.appendSourceLink(source)
+ if (isVar) {
+ node.appendTextNode("var", NodeKind.Modifier)
}
- }
- setter?.let {
- if (!it.isDefault) {
- node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter")
+
+ if (isConst) {
+ val psi = sourcePsi()
+ val valueText = when (psi) {
+ is KtVariableDeclaration -> psi.initializer?.text
+ is PsiField -> psi.initializer?.text
+ else -> null
+ }
+ valueText?.let { node.appendTextNode(it, NodeKind.Value) }
+ }
+
+
+ getter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter")
+ }
+ }
+ setter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter")
+ }
}
+ node.appendDefaultPlatforms(this)
+ }
+ if (external) {
+ node.appendExternalLink(this)
}
overriddenDescriptors.forEach {
addOverrideLink(it, this)
}
- node.appendDefaultPlatforms(this)
register(this, node)
return node
@@ -779,21 +874,55 @@ class DocumentationBuilder
"\"" + StringUtil.escapeStringCharacters(value) + "\""
is EnumEntrySyntheticClassDescriptor ->
value.containingDeclaration.name.asString() + "." + value.name.asString()
+ is Pair<*, *> -> {
+ val (classId, name) = value
+ if (classId is ClassId && name is Name) {
+ classId.shortClassName.asString() + "." + name.asString()
+ } else {
+ value.toString()
+ }
+ }
else -> value.toString()
}.let { valueString ->
DocumentationNode(valueString, Content.Empty, NodeKind.Value)
}
}
-}
-val visibleToDocumentation = setOf(Visibilities.PROTECTED, Visibilities.PUBLIC)
-fun DeclarationDescriptor.isDocumented(options: DocumentationOptions): Boolean {
- return (options.effectivePackageOptions(fqNameSafe).includeNonPublic
+ fun DocumentationNode.getParentForPackageMember(
+ descriptor: DeclarationDescriptor,
+ externalClassNodes: MutableMap<FqName, DocumentationNode>,
+ allFqNames: Collection<FqName>,
+ packageName: FqName
+ ): DocumentationNode {
+ if (descriptor is CallableMemberDescriptor) {
+ val extensionClassDescriptor = descriptor.getExtensionClassDescriptor()
+ if (extensionClassDescriptor != null && isExtensionForExternalClass(descriptor, extensionClassDescriptor, allFqNames) &&
+ !ErrorUtils.isError(extensionClassDescriptor)) {
+ val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor)
+ return externalClassNodes.getOrPut(fqName) {
+ val newNode = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.ExternalClass)
+ val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(extensionClassDescriptor)
+ if (externalLink != null) {
+ newNode.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ }
+ append(newNode, RefKind.Member)
+ refGraph.register("${packageName.asString()}:${extensionClassDescriptor.signature()}", newNode)
+ newNode
+ }
+ }
+ }
+ return this
+ }
+
+}
+
+fun DeclarationDescriptor.isDocumented(passConfiguration: DokkaConfiguration.PassConfiguration): Boolean {
+ return (passConfiguration.effectivePackageOptions(fqNameSafe).includeNonPublic
|| this !is MemberDescriptor
- || this.visibility in visibleToDocumentation)
- && !isDocumentationSuppressed(options)
- && (!options.effectivePackageOptions(fqNameSafe).skipDeprecated || !isDeprecated())
+ || this.visibility.isPublicAPI)
+ && !isDocumentationSuppressed(passConfiguration)
+ && (!passConfiguration.effectivePackageOptions(fqNameSafe).skipDeprecated || !isDeprecated())
}
private fun DeclarationDescriptor.isGenerated() = this is CallableMemberDescriptor && kind != CallableMemberDescriptor.Kind.DECLARATION
@@ -807,8 +936,13 @@ class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder {
val externalClassNodes = hashMapOf<FqName, DocumentationNode>()
declarations.forEach { descriptor ->
with(documentationBuilder) {
- if (descriptor.isDocumented(options)) {
- val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes, allFqNames)
+ if (descriptor.isDocumented(passConfiguration)) {
+ val parent = packageNode.getParentForPackageMember(
+ descriptor,
+ externalClassNodes,
+ allFqNames,
+ packageName
+ )
parent.appendOrUpdateMember(descriptor)
}
}
@@ -819,20 +953,15 @@ class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder {
class KotlinJavaDocumentationBuilder
@Inject constructor(val resolutionFacade: DokkaResolutionFacade,
val documentationBuilder: DocumentationBuilder,
- val options: DocumentationOptions,
+ val passConfiguration: DokkaConfiguration.PassConfiguration,
val logger: DokkaLogger) : JavaDocumentationBuilder {
override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
val classDescriptors = file.classes.map {
- val javaDescriptorResolver = resolutionFacade.getFrontendService(JavaDescriptorResolver::class.java)
-
- javaDescriptorResolver.resolveClass(JavaClassImpl(it)) ?: run {
- logger.warn("Cannot find descriptor for Java class ${it.qualifiedName}")
- null
- }
+ it.getJavaClassDescriptor(resolutionFacade)
}
- if (classDescriptors.any { it != null && it.isDocumented(options) }) {
- val packageNode = module.findOrCreatePackageNode(file.packageName, packageContent, documentationBuilder.refGraph)
+ if (classDescriptors.any { it != null && it.isDocumented(passConfiguration) }) {
+ val packageNode = findOrCreatePackageNode(module, file.packageName, packageContent, documentationBuilder.refGraph)
for (descriptor in classDescriptors.filterNotNull()) {
with(documentationBuilder) {
@@ -861,13 +990,13 @@ fun AnnotationDescriptor.mustBeDocumented(): Boolean {
return annotationClass.isDocumentedAnnotation()
}
-fun DeclarationDescriptor.isDocumentationSuppressed(options: DocumentationOptions): Boolean {
+fun DeclarationDescriptor.isDocumentationSuppressed(passConfiguration: DokkaConfiguration.PassConfiguration): Boolean {
- if (options.effectivePackageOptions(fqNameSafe).suppress) return true
+ if (passConfiguration.effectivePackageOptions(fqNameSafe).suppress) return true
val path = this.findPsi()?.containingFile?.virtualFile?.path
if (path != null) {
- if (File(path).absoluteFile in options.suppressedFiles) return true
+ if (path in passConfiguration.suppressedFiles) return true
}
val doc = findKDoc()
@@ -877,30 +1006,12 @@ fun DeclarationDescriptor.isDocumentationSuppressed(options: DocumentationOption
}
fun DeclarationDescriptor.sourcePsi() =
- ((original as DeclarationDescriptorWithSource).source as? PsiSourceElement)?.psi
+ ((original as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi
fun DeclarationDescriptor.isDeprecated(): Boolean = annotations.any {
DescriptorUtils.getFqName(it.type.constructor.declarationDescriptor!!).asString() == "kotlin.Deprecated"
} || (this is ConstructorDescriptor && containingDeclaration.isDeprecated())
-fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor,
- externalClassNodes: MutableMap<FqName, DocumentationNode>,
- allFqNames: Collection<FqName>): DocumentationNode {
- if (descriptor is CallableMemberDescriptor) {
- val extensionClassDescriptor = descriptor.getExtensionClassDescriptor()
- if (extensionClassDescriptor != null && isExtensionForExternalClass(descriptor, extensionClassDescriptor, allFqNames) &&
- !ErrorUtils.isError(extensionClassDescriptor)) {
- val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor)
- return externalClassNodes.getOrPut(fqName, {
- val newNode = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.ExternalClass)
- append(newNode, RefKind.Member)
- newNode
- })
- }
- }
- return this
-}
-
fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? {
val extensionReceiver = extensionReceiverParameter
if (extensionReceiver != null) {
@@ -923,7 +1034,7 @@ fun DeclarationDescriptor.signature(): String {
is TypeAliasDescriptor -> DescriptorUtils.getFqName(this).asString()
is PropertyDescriptor -> containingDeclaration.signature() + "$" + name + receiverSignature()
- is FunctionDescriptor -> containingDeclaration.signature() + "$" + name + parameterSignature()
+ is FunctionDescriptor -> containingDeclaration.signature() + "$" + name + parameterSignature() + ":" + returnType?.signature()
is ValueParameterDescriptor -> containingDeclaration.signature() + "/" + name
is TypeParameterDescriptor -> containingDeclaration.signature() + "*" + name
is ReceiverParameterDescriptor -> containingDeclaration.signature() + "/" + name
@@ -984,8 +1095,8 @@ fun DeclarationDescriptor.sourceLocation(): String? {
return null
}
-fun DocumentationModule.prepareForGeneration(options: DocumentationOptions) {
- if (options.generateIndexPages) {
+fun DocumentationModule.prepareForGeneration(configuration: DokkaConfiguration) {
+ if (configuration.generateIndexPages) {
generateAllTypesNode()
}
nodeRefGraph.resolveReferences()
@@ -993,8 +1104,10 @@ fun DocumentationModule.prepareForGeneration(options: DocumentationOptions) {
fun DocumentationNode.generateAllTypesNode() {
val allTypes = members(NodeKind.Package)
- .flatMap { it.members.filter { it.kind in NodeKind.classLike || it.kind == NodeKind.ExternalClass } }
- .sortedBy { if (it.kind == NodeKind.ExternalClass) it.name.substringAfterLast('.') else it.name }
+ .flatMap { it.members.filter {
+ it.kind in NodeKind.classLike || it.kind == NodeKind.ExternalClass
+ || (it.kind == NodeKind.GroupNode && it.origins.all { it.kind in NodeKind.classLike }) } }
+ .sortedBy { if (it.kind == NodeKind.ExternalClass) it.name.substringAfterLast('.').toLowerCase() else it.name.toLowerCase() }
val allTypesNode = DocumentationNode("alltypes", Content.Empty, NodeKind.AllTypes)
for (typeNode in allTypes) {
@@ -1003,3 +1116,18 @@ fun DocumentationNode.generateAllTypesNode() {
append(allTypesNode, RefKind.Member)
}
+
+fun ClassDescriptor.supertypesWithAnyPrecise(): Collection<KotlinType> {
+ if (KotlinBuiltIns.isAny(this)) {
+ return emptyList()
+ }
+ return typeConstructor.supertypesWithAny()
+}
+
+fun PassConfiguration.effectivePackageOptions(pack: String): DokkaConfiguration.PackageOptions {
+ val rootPackageOptions = PackageOptionsImpl("", includeNonPublic, reportUndocumented, skipDeprecated)
+ return perPackageOptions.firstOrNull { pack == it.prefix || pack.startsWith(it.prefix + ".") } ?: rootPackageOptions
+}
+
+fun PassConfiguration.effectivePackageOptions(pack: FqName): DokkaConfiguration.PackageOptions = effectivePackageOptions(pack.asString())
+
diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
index 108cee78..9d986aee 100644
--- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
@@ -2,13 +2,16 @@ package org.jetbrains.dokka
import com.google.inject.Inject
import com.google.inject.Singleton
+import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.util.io.*
+import org.jetbrains.dokka.Formats.FileGeneratorBasedFormatDescriptor
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import org.jetbrains.dokka.Utilities.ServiceLocator
+import org.jetbrains.dokka.Utilities.lookup
import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
-import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
-import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
-import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
+import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
+import org.jetbrains.kotlin.load.java.descriptors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -19,26 +22,48 @@ import java.net.HttpURLConnection
import java.net.URL
import java.net.URLConnection
import java.nio.file.Path
+import java.nio.file.Paths
import java.security.MessageDigest
+import javax.inject.Named
+import kotlin.reflect.full.findAnnotation
fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) }
+typealias PackageFqNameToLocation = MutableMap<FqName, PackageListProvider.ExternalDocumentationRoot>
+
@Singleton
-class ExternalDocumentationLinkResolver @Inject constructor(
- val options: DocumentationOptions,
- val logger: DokkaLogger
+class PackageListProvider @Inject constructor(
+ val configuration: DokkaConfiguration,
+ val logger: DokkaLogger
) {
+ val storage = mutableMapOf<DokkaConfiguration.ExternalDocumentationLink, PackageFqNameToLocation>()
- val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>()
- val formats = mutableMapOf<String, InboundExternalLinkResolutionService>()
+ val cacheDir: Path? = when {
+ configuration.cacheRoot == "default" -> Paths.get(System.getProperty("user.home"), ".cache", "dokka")
+ configuration.cacheRoot != null -> Paths.get(configuration.cacheRoot)
+ else -> null
+ }?.resolve("packageListCache")?.apply { createDirectories() }
- class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>) {
- override fun toString(): String = rootUrl.toString()
+ val cachedProtocols = setOf("http", "https", "ftp")
+
+ init {
+ for (conf in configuration.passesConfigurations) {
+ for (link in conf.externalDocumentationLinks) {
+ if (link in storage) {
+ continue
+ }
+
+ try {
+ loadPackageList(link)
+ } catch (e: Exception) {
+ throw RuntimeException("Exception while loading package-list from $link", e)
+ }
+ }
+
+ }
}
- val cacheDir: Path? = options.cacheRoot?.resolve("packageListCache")?.apply { createDirectories() }
- val cachedProtocols = setOf("http", "https", "ftp")
fun URL.doOpenConnectionToReadContent(timeout: Int = 10000, redirectsAllowed: Int = 16): URLConnection {
val connection = this.openConnection()
@@ -114,48 +139,99 @@ class ExternalDocumentationLinkResolver @Inject constructor(
val (params, packages) =
packageListStream
- .bufferedReader()
- .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } }
+ .bufferedReader()
+ .useLines { lines -> lines.partition { it.startsWith(ExternalDocumentationLinkResolver.DOKKA_PARAM_PREFIX) } }
val paramsMap = params.asSequence()
- .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) }
- .groupBy({ (key, _) -> key }, { (_, value) -> value })
+ .map { it.removePrefix(ExternalDocumentationLinkResolver.DOKKA_PARAM_PREFIX).split(":", limit = 2) }
+ .groupBy({ (key, _) -> key }, { (_, value) -> value })
val format = paramsMap["format"]?.singleOrNull() ?: "javadoc"
val locations = paramsMap["location"].orEmpty()
- .map { it.split("\u001f", limit = 2) }
- .map { (key, value) -> key to value }
- .toMap()
+ .map { it.split("\u001f", limit = 2) }
+ .map { (key, value) -> key to value }
+ .toMap()
- val resolver = if (format == "javadoc") {
- InboundExternalLinkResolutionService.Javadoc()
- } else {
- val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?:
- throw RuntimeException("Failed to parse package list from $packageListUrl")
- InboundExternalLinkResolutionService.Dokka(linkExtension)
- }
+
+ val defaultResolverDesc = ExternalDocumentationLinkResolver.services["dokka-default"]!!
+ val resolverDesc = ExternalDocumentationLinkResolver.services[format]
+ ?: defaultResolverDesc.takeIf { format in formatsWithDefaultResolver }
+ ?: defaultResolverDesc.also {
+ logger.warn("Couldn't find InboundExternalLinkResolutionService(format = `$format`) for $link, using Dokka default")
+ }
+
+
+ val resolverClass = javaClass.classLoader.loadClass(resolverDesc.className).kotlin
+
+ val constructors = resolverClass.constructors
+
+ val constructor = constructors.singleOrNull()
+ ?: constructors.first { it.findAnnotation<Inject>() != null }
+ val resolver = constructor.call(paramsMap) as InboundExternalLinkResolutionService
val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
- packages.map { FqName(it) }.forEach { packageFqNameToLocation[it] = rootInfo }
+ val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>()
+ storage[link] = packageFqNameToLocation
+
+ val fqNames = packages.map { FqName(it) }
+ for(name in fqNames) {
+ packageFqNameToLocation[name] = rootInfo
+ }
}
+
+
+ class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>) {
+ override fun toString(): String = rootUrl.toString()
+ }
+
+ companion object {
+ private val formatsWithDefaultResolver =
+ ServiceLocator
+ .allServices("format")
+ .filter {
+ val desc = ServiceLocator.lookup<FormatDescriptor>(it) as? FileGeneratorBasedFormatDescriptor
+ desc?.generatorServiceClass == FileGenerator::class
+ }.map { it.name }
+ .toSet()
+
+ }
+
+}
+
+class ExternalDocumentationLinkResolver @Inject constructor(
+ val configuration: DokkaConfiguration,
+ val passConfiguration: DokkaConfiguration.PassConfiguration,
+ @Named("libraryResolutionFacade") val libraryResolutionFacade: DokkaResolutionFacade,
+ val logger: DokkaLogger,
+ val packageListProvider: PackageListProvider
+) {
+
+ val formats = mutableMapOf<String, InboundExternalLinkResolutionService>()
+ val packageFqNameToLocation = mutableMapOf<FqName, PackageListProvider.ExternalDocumentationRoot>()
+
init {
- options.externalDocumentationLinks.forEach {
- try {
- loadPackageList(it)
- } catch (e: Exception) {
- throw RuntimeException("Exception while loading package-list from $it", e)
- }
+ val fqNameToLocationMaps = passConfiguration.externalDocumentationLinks
+ .mapNotNull { packageListProvider.storage[it] }
+
+ for(map in fqNameToLocationMaps) {
+ packageFqNameToLocation.putAll(map)
+ }
+ }
+
+ fun buildExternalDocumentationLink(element: PsiElement): String? {
+ return element.extractDescriptor(libraryResolutionFacade)?.let {
+ buildExternalDocumentationLink(it)
}
}
fun buildExternalDocumentationLink(symbol: DeclarationDescriptor): String? {
val packageFqName: FqName =
when (symbol) {
- is DeclarationDescriptorNonRoot -> symbol.parents.firstOrNull { it is PackageFragmentDescriptor }?.fqNameSafe ?: return null
is PackageFragmentDescriptor -> symbol.fqName
+ is DeclarationDescriptorNonRoot -> symbol.parents.firstOrNull { it is PackageFragmentDescriptor }?.fqNameSafe ?: return null
else -> return null
}
@@ -169,6 +245,7 @@ class ExternalDocumentationLinkResolver @Inject constructor(
companion object {
const val DOKKA_PARAM_PREFIX = "\$dokka."
+ val services = ServiceLocator.allServices("inbound-link-resolver").associateBy { it.name }
}
}
@@ -176,15 +253,17 @@ class ExternalDocumentationLinkResolver @Inject constructor(
interface InboundExternalLinkResolutionService {
fun getPath(symbol: DeclarationDescriptor): String?
- class Javadoc : InboundExternalLinkResolutionService {
+ class Javadoc(paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
override fun getPath(symbol: DeclarationDescriptor): String? {
- if (symbol is JavaClassDescriptor) {
+ if (symbol is EnumEntrySyntheticClassDescriptor) {
+ return getPath(symbol.containingDeclaration)?.let { it + "#" + symbol.name.asString() }
+ } else if (symbol is JavaClassDescriptor) {
return DescriptorUtils.getFqName(symbol).asString().replace(".", "/") + ".html"
} else if (symbol is JavaCallableMemberDescriptor) {
val containingClass = symbol.containingDeclaration as? JavaClassDescriptor ?: return null
val containingClassLink = getPath(containingClass)
if (containingClassLink != null) {
- if (symbol is JavaMethodDescriptor) {
+ if (symbol is JavaMethodDescriptor || symbol is JavaClassConstructorDescriptor) {
val psi = symbol.sourcePsi() as? PsiMethod
if (psi != null) {
val params = psi.parameterList.parameters.joinToString { it.type.canonicalText }
@@ -200,7 +279,9 @@ interface InboundExternalLinkResolutionService {
}
}
- class Dokka(val extension: String) : InboundExternalLinkResolutionService {
+ class Dokka(val paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+ val extension = paramsMap["linkExtension"]?.singleOrNull() ?: error("linkExtension not provided for Dokka resolver")
+
override fun getPath(symbol: DeclarationDescriptor): String? {
val leafElement = when (symbol) {
is CallableDescriptor, is TypeAliasDescriptor -> true
@@ -211,13 +292,11 @@ interface InboundExternalLinkResolutionService {
else return "$path/index.$extension"
}
- fun getPathWithoutExtension(symbol: DeclarationDescriptor): String {
- if (symbol.containingDeclaration == null)
- return identifierToFilename(symbol.name.asString())
- else if (symbol is PackageFragmentDescriptor) {
- return symbol.fqName.asString()
- } else {
- return getPathWithoutExtension(symbol.containingDeclaration!!) + '/' + identifierToFilename(symbol.name.asString())
+ private fun getPathWithoutExtension(symbol: DeclarationDescriptor): String {
+ return when {
+ symbol.containingDeclaration == null -> identifierToFilename(symbol.name.asString())
+ symbol is PackageFragmentDescriptor -> identifierToFilename(symbol.fqName.asString())
+ else -> getPathWithoutExtension(symbol.containingDeclaration!!) + '/' + identifierToFilename(symbol.name.asString())
}
}
diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt
deleted file mode 100644
index a3be658e..00000000
--- a/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.jetbrains.dokka.Kotlin
-
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.getSignature
-import org.jetbrains.dokka.sourcePsi
-import org.jetbrains.kotlin.asJava.toLightElements
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.psi.KtElement
-
-class KotlinAsJavaDescriptorSignatureProvider : DescriptorSignatureProvider {
- override fun signature(forDesc: DeclarationDescriptor): String {
- val sourcePsi = forDesc.sourcePsi()
- val javaLikePsi = if (sourcePsi is KtElement) {
- sourcePsi.toLightElements().firstOrNull()
- } else {
- sourcePsi
- }
-
- return getSignature(javaLikePsi) ?:
- "not implemented for $forDesc with psi: $sourcePsi"
- }
-} \ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
index c7ed8292..be6dd2e1 100644
--- a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
+++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
@@ -28,7 +28,7 @@ class KotlinAsJavaDocumentationBuilder
return
}
- val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options,
+ val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.passConfiguration,
documentationBuilder.refGraph,
kotlinAsJavaDocumentationParser)
diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt
new file mode 100644
index 00000000..20ea179e
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt
@@ -0,0 +1,25 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.asJava.toLightElements
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.psi.KtElement
+
+class KotlinAsJavaElementSignatureProvider : ElementSignatureProvider {
+
+ private fun PsiElement.javaLikePsi() = when {
+ this is KtElement -> toLightElements().firstOrNull()
+ else -> this
+ }
+
+ override fun signature(forPsi: PsiElement): String {
+ return getSignature(forPsi.javaLikePsi()) ?:
+ "not implemented for $forPsi"
+ }
+
+ override fun signature(forDesc: DeclarationDescriptor): String {
+ val sourcePsi = forDesc.sourcePsi()
+ return getSignature(sourcePsi?.javaLikePsi()) ?:
+ "not implemented for $forDesc with psi: $sourcePsi"
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt
deleted file mode 100644
index 7ecd0389..00000000
--- a/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.jetbrains.dokka.Kotlin
-
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.signature
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-
-class KotlinDescriptorSignatureProvider : DescriptorSignatureProvider {
- override fun signature(forDesc: DeclarationDescriptor): String = forDesc.signature()
-} \ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt
new file mode 100644
index 00000000..bcac0182
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMember
+import com.intellij.psi.PsiPackage
+import org.jetbrains.kotlin.asJava.elements.KtLightElement
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.BindingContext
+import javax.inject.Inject
+
+class KotlinElementSignatureProvider @Inject constructor(
+ val resolutionFacade: DokkaResolutionFacade
+) : ElementSignatureProvider {
+ override fun signature(forPsi: PsiElement): String {
+ return forPsi.extractDescriptor(resolutionFacade)
+ ?.let { signature(it) }
+ ?: run { "no desc for $forPsi in ${(forPsi as? PsiMember)?.containingClass}" }
+ }
+
+ override fun signature(forDesc: DeclarationDescriptor): String = forDesc.signature()
+}
+
+
+fun PsiElement.extractDescriptor(resolutionFacade: DokkaResolutionFacade): DeclarationDescriptor? {
+ val forPsi = this
+
+ return when (forPsi) {
+ is KtLightElement<*, *> -> return (forPsi.kotlinOrigin!!).extractDescriptor(resolutionFacade)
+ is PsiPackage -> resolutionFacade.moduleDescriptor.getPackage(FqName(forPsi.qualifiedName))
+ is PsiMember -> forPsi.getJavaOrKotlinMemberDescriptor(resolutionFacade)
+ else -> resolutionFacade.resolveSession.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, forPsi]
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
index f33c8c96..5b565464 100644
--- a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
+++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
@@ -5,8 +5,13 @@ import org.jetbrains.dokka.LanguageService.RenderMode
/**
* Implements [LanguageService] and provides rendering of symbols in Kotlin language
*/
-class KotlinLanguageService : LanguageService {
- private val fullOnlyModifiers = setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified")
+class KotlinLanguageService : CommonLanguageService() {
+ override fun showModifierInSummary(node: DocumentationNode): Boolean {
+ return node.name !in fullOnlyModifiers
+ }
+
+ private val fullOnlyModifiers =
+ setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified")
override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode {
return content {
@@ -17,11 +22,12 @@ class KotlinLanguageService : LanguageService {
NodeKind.EnumItem,
NodeKind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name)
+ NodeKind.Parameter -> renderParameter(node, renderMode)
NodeKind.TypeParameter -> renderTypeParameter(node, renderMode)
NodeKind.Type,
NodeKind.UpperBound -> renderType(node, renderMode)
- NodeKind.Modifier -> renderModifier(node)
+ NodeKind.Modifier -> renderModifier(this, node, renderMode)
NodeKind.Constructor,
NodeKind.Function,
NodeKind.CompanionObjectFunction -> renderFunction(node, renderMode)
@@ -32,12 +38,6 @@ class KotlinLanguageService : LanguageService {
}
}
- override fun renderName(node: DocumentationNode): String {
- return when (node.kind) {
- NodeKind.Constructor -> node.owner!!.name
- else -> node.name
- }
- }
override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? {
if (nodes.size < 2) return null
@@ -46,10 +46,17 @@ class KotlinLanguageService : LanguageService {
return content {
val typeParameter = functionWithTypeParameter.details(NodeKind.TypeParameter).first()
if (functionWithTypeParameter.kind == NodeKind.Function) {
- renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
- }
- else {
- renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
+ renderFunction(
+ functionWithTypeParameter,
+ RenderMode.SUMMARY,
+ SummarizingMapper(receiverKind, typeParameter.name)
+ )
+ } else {
+ renderProperty(
+ functionWithTypeParameter,
+ RenderMode.SUMMARY,
+ SummarizingMapper(receiverKind, typeParameter.name)
+ )
}
}
}
@@ -70,26 +77,27 @@ class KotlinLanguageService : LanguageService {
companion object {
private val arrayClasses = setOf(
- "kotlin.Array",
- "kotlin.BooleanArray",
- "kotlin.ByteArray",
- "kotlin.CharArray",
- "kotlin.ShortArray",
- "kotlin.IntArray",
- "kotlin.LongArray",
- "kotlin.FloatArray",
- "kotlin.DoubleArray"
+ "kotlin.Array",
+ "kotlin.BooleanArray",
+ "kotlin.ByteArray",
+ "kotlin.CharArray",
+ "kotlin.ShortArray",
+ "kotlin.IntArray",
+ "kotlin.LongArray",
+ "kotlin.FloatArray",
+ "kotlin.DoubleArray"
)
private val arrayOrListClasses = setOf("kotlin.List") + arrayClasses
private val iterableClasses = setOf(
- "kotlin.Collection",
- "kotlin.Sequence",
- "kotlin.Iterable",
- "kotlin.Map",
- "kotlin.String",
- "kotlin.CharSequence") + arrayOrListClasses
+ "kotlin.Collection",
+ "kotlin.Sequence",
+ "kotlin.Iterable",
+ "kotlin.Map",
+ "kotlin.String",
+ "kotlin.CharSequence"
+ ) + arrayOrListClasses
}
private enum class ReceiverKind(val receiverName: String, val classes: Collection<String>) {
@@ -102,53 +110,21 @@ class KotlinLanguageService : LanguageService {
fun renderReceiver(receiver: DocumentationNode, to: ContentBlock)
}
- private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String): SignatureMapper {
+ private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String) : SignatureMapper {
override fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) {
to.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName))
to.text("<$typeParameterName>")
}
}
- private fun ContentBlock.renderPackage(node: DocumentationNode) {
- keyword("package")
- text(" ")
- identifier(node.name)
- }
-
- private fun <T> ContentBlock.renderList(nodes: List<T>, separator: String = ", ",
- noWrap: Boolean = false, renderItem: (T) -> Unit) {
- if (nodes.none())
- return
- renderItem(nodes.first())
- nodes.drop(1).forEach {
- if (noWrap) {
- symbol(separator.removeSuffix(" "))
- nbsp()
- } else {
- symbol(separator)
- }
- renderItem(it)
- }
- }
-
- private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode)->Unit) {
- val to = node.links.firstOrNull()
- if (to == null)
- body(node)
- else
- link(to) {
- body(node)
- }
- }
-
private fun ContentBlock.renderFunctionalTypeParameterName(node: DocumentationNode, renderMode: RenderMode) {
node.references(RefKind.HiddenAnnotation).map { it.to }
- .find { it.name == "ParameterName" }?.let {
- val parameterNameValue = it.detail(NodeKind.Parameter).detail(NodeKind.Value)
- identifier(parameterNameValue.name.removeSurrounding("\""), IdentifierKind.ParameterName)
- symbol(":")
- nbsp()
- }
+ .find { it.name == "ParameterName" }?.let {
+ val parameterNameValue = it.detail(NodeKind.Parameter).detail(NodeKind.Value)
+ identifier(parameterNameValue.name.removeSurrounding("\""), IdentifierKind.ParameterName)
+ symbol(":")
+ nbsp()
+ }
}
private fun ContentBlock.renderFunctionalType(node: DocumentationNode, renderMode: RenderMode) {
@@ -198,7 +174,9 @@ class KotlinLanguageService : LanguageService {
renderAnnotationsForNode(node)
}
renderModifiersForNode(node, renderMode, true)
- renderLinked(node) { identifier(it.name, IdentifierKind.TypeName) }
+ renderLinked(this, node) {
+ identifier(it.typeDeclarationClass?.classNodeNameWithOuterClass() ?: it.name, IdentifierKind.TypeName)
+ }
val typeArguments = node.details(NodeKind.Type)
if (typeArguments.isNotEmpty()) {
symbol("<")
@@ -213,16 +191,18 @@ class KotlinLanguageService : LanguageService {
}
}
- private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) {
+ override fun renderModifier(
+ block: ContentBlock,
+ node: DocumentationNode,
+ renderMode: RenderMode,
+ nowrap: Boolean
+ ) {
when (node.name) {
- "final", "public", "var" -> {}
+ "final", "public", "var", "expect", "actual" -> {
+ }
else -> {
- keyword(node.name)
- if (nowrap) {
- nbsp()
- }
- else {
- text(" ")
+ if (node.name !in fullOnlyModifiers || renderMode == RenderMode.FULL) {
+ super.renderModifier(block, node, renderMode, nowrap)
}
}
}
@@ -238,11 +218,12 @@ class KotlinLanguageService : LanguageService {
nbsp()
symbol(":")
nbsp()
- renderList(constraints, noWrap=true) {
+ renderList(constraints, noWrap = true) {
renderType(it, renderMode)
}
}
}
+
private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) {
if (renderMode == RenderMode.FULL) {
renderAnnotationsForNode(node)
@@ -274,9 +255,12 @@ class KotlinLanguageService : LanguageService {
}
private fun ContentBlock.renderExtraTypeParameterConstraints(node: DocumentationNode, renderMode: RenderMode) {
- val parametersWithMultipleConstraints = node.details(NodeKind.TypeParameter).filter { it.details(NodeKind.UpperBound).size > 1 }
+ val parametersWithMultipleConstraints =
+ node.details(NodeKind.TypeParameter).filter { it.details(NodeKind.UpperBound).size > 1 }
val parametersWithConstraints = parametersWithMultipleConstraints
- .flatMap { parameter -> parameter.details(NodeKind.UpperBound).map { constraint -> parameter to constraint } }
+ .flatMap { parameter ->
+ parameter.details(NodeKind.UpperBound).map { constraint -> parameter to constraint }
+ }
if (parametersWithMultipleConstraints.isNotEmpty()) {
keyword(" where ")
renderList(parametersWithConstraints) {
@@ -290,7 +274,7 @@ class KotlinLanguageService : LanguageService {
}
private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) {
- val supertypes = node.details(NodeKind.Supertype)
+ val supertypes = node.details(NodeKind.Supertype).filterNot { it.qualifiedNameFromType() in ignoredSupertypes }
if (supertypes.any()) {
nbsp()
symbol(":")
@@ -302,20 +286,6 @@ class KotlinLanguageService : LanguageService {
}
}
- private fun ContentBlock.renderModifiersForNode(node: DocumentationNode,
- renderMode: RenderMode,
- nowrap: Boolean = false) {
- val modifiers = node.details(NodeKind.Modifier)
- for (it in modifiers) {
- if (node.kind == org.jetbrains.dokka.NodeKind.Interface && it.name == "abstract")
- continue
- if (renderMode == RenderMode.SUMMARY && it.name in fullOnlyModifiers) {
- continue
- }
- renderModifier(it, nowrap)
- }
- }
-
private fun ContentBlock.renderAnnotationsForNode(node: DocumentationNode) {
node.annotations.forEach {
renderAnnotation(it)
@@ -365,9 +335,11 @@ class KotlinLanguageService : LanguageService {
}
}
- private fun ContentBlock.renderFunction(node: DocumentationNode,
- renderMode: RenderMode,
- signatureMapper: SignatureMapper? = null) {
+ private fun ContentBlock.renderFunction(
+ node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null
+ ) {
if (renderMode == RenderMode.FULL) {
renderAnnotationsForNode(node)
}
@@ -385,7 +357,7 @@ class KotlinLanguageService : LanguageService {
renderReceiver(node, renderMode, signatureMapper)
- if (node.kind != org.jetbrains.dokka.NodeKind.Constructor)
+ if (node.kind != NodeKind.Constructor)
identifierOrDeprecated(node)
symbol("(")
@@ -401,14 +373,17 @@ class KotlinLanguageService : LanguageService {
symbol(")")
symbol(": ")
renderType(node.detail(NodeKind.Type), renderMode)
- }
- else {
+ } else {
symbol(")")
}
renderExtraTypeParameterConstraints(node, renderMode)
}
- private fun ContentBlock.renderReceiver(node: DocumentationNode, renderMode: RenderMode, signatureMapper: SignatureMapper?) {
+ private fun ContentBlock.renderReceiver(
+ node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper?
+ ) {
val receiver = node.details(NodeKind.Receiver).singleOrNull()
if (receiver != null) {
if (signatureMapper != null) {
@@ -428,17 +403,19 @@ class KotlinLanguageService : LanguageService {
}
}
- private fun needReturnType(node: DocumentationNode) = when(node.kind) {
+ private fun needReturnType(node: DocumentationNode) = when (node.kind) {
NodeKind.Constructor -> false
else -> !node.isUnitReturnType()
}
fun DocumentationNode.isUnitReturnType(): Boolean =
- detail(NodeKind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit"
+ detail(NodeKind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit"
- private fun ContentBlock.renderProperty(node: DocumentationNode,
- renderMode: RenderMode,
- signatureMapper: SignatureMapper? = null) {
+ private fun ContentBlock.renderProperty(
+ node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null
+ ) {
if (renderMode == RenderMode.FULL) {
renderAnnotationsForNode(node)
}
@@ -462,7 +439,7 @@ class KotlinLanguageService : LanguageService {
}
fun DocumentationNode.getPropertyKeyword() =
- if (details(NodeKind.Modifier).any { it.name == "var" }) "var" else "val"
+ if (details(NodeKind.Modifier).any { it.name == "var" }) "var" else "val"
fun ContentBlock.identifierOrDeprecated(node: DocumentationNode) {
if (node.deprecation != null) {
@@ -475,4 +452,12 @@ class KotlinLanguageService : LanguageService {
}
}
-fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name
+fun DocumentationNode.qualifiedNameFromType(): String {
+ return details.firstOrNull { it.kind == NodeKind.QualifiedName }?.name
+ ?: (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName()
+ ?: name
+}
+
+
+val DocumentationNode.typeDeclarationClass
+ get() = (links.firstOrNull { it.kind in NodeKind.classLike } ?: externalType)
diff --git a/core/src/main/kotlin/Languages/CommonLanguageService.kt b/core/src/main/kotlin/Languages/CommonLanguageService.kt
new file mode 100644
index 00000000..ddc95d32
--- /dev/null
+++ b/core/src/main/kotlin/Languages/CommonLanguageService.kt
@@ -0,0 +1,84 @@
+package org.jetbrains.dokka
+
+
+abstract class CommonLanguageService : LanguageService {
+
+ protected fun ContentBlock.renderPackage(node: DocumentationNode) {
+ keyword("package")
+ nbsp()
+ identifier(node.name)
+ }
+
+ override fun renderName(node: DocumentationNode): String {
+ return when (node.kind) {
+ NodeKind.Constructor -> node.owner!!.name
+ else -> node.name
+ }
+ }
+
+ open fun renderModifier(
+ block: ContentBlock,
+ node: DocumentationNode,
+ renderMode: LanguageService.RenderMode,
+ nowrap: Boolean = false
+ ) = with(block) {
+ keyword(node.name)
+ if (nowrap) {
+ nbsp()
+ } else {
+ text(" ")
+ }
+ }
+
+ protected fun renderLinked(
+ block: ContentBlock,
+ node: DocumentationNode,
+ body: ContentBlock.(DocumentationNode) -> Unit
+ ) = with(block) {
+ val to = node.links.firstOrNull()
+ if (to == null)
+ body(node)
+ else
+ link(to) {
+ this.body(node)
+ }
+ }
+
+ protected fun <T> ContentBlock.renderList(
+ nodes: List<T>, separator: String = ", ",
+ noWrap: Boolean = false, renderItem: (T) -> Unit
+ ) {
+ if (nodes.none())
+ return
+ renderItem(nodes.first())
+ nodes.drop(1).forEach {
+ if (noWrap) {
+ symbol(separator.removeSuffix(" "))
+ nbsp()
+ } else {
+ symbol(separator)
+ }
+ renderItem(it)
+ }
+ }
+
+ abstract fun showModifierInSummary(node: DocumentationNode): Boolean
+
+ protected fun ContentBlock.renderModifiersForNode(
+ node: DocumentationNode,
+ renderMode: LanguageService.RenderMode,
+ nowrap: Boolean = false
+ ) {
+ val modifiers = node.details(NodeKind.Modifier)
+ for (it in modifiers) {
+ if (node.kind == NodeKind.Interface && it.name == "abstract")
+ continue
+ if (renderMode == LanguageService.RenderMode.SUMMARY && !showModifierInSummary(it)) {
+ continue
+ }
+ renderModifier(this, it, renderMode, nowrap)
+ }
+ }
+
+
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Languages/NewJavaLanguageService.kt b/core/src/main/kotlin/Languages/NewJavaLanguageService.kt
new file mode 100644
index 00000000..992cd090
--- /dev/null
+++ b/core/src/main/kotlin/Languages/NewJavaLanguageService.kt
@@ -0,0 +1,197 @@
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.LanguageService.RenderMode
+
+/**
+ * Implements [LanguageService] and provides rendering of symbols in Java language
+ */
+class NewJavaLanguageService : CommonLanguageService() {
+ override fun showModifierInSummary(node: DocumentationNode): Boolean {
+ return true
+ }
+
+ override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode {
+ return content {
+ (when (node.kind) {
+ NodeKind.Package -> renderPackage(node)
+ in NodeKind.classLike -> renderClass(node, renderMode)
+
+ NodeKind.Modifier -> renderModifier(this, node, renderMode)
+ NodeKind.TypeParameter -> renderTypeParameter(node)
+ NodeKind.Type,
+ NodeKind.UpperBound -> renderType(node)
+ NodeKind.Parameter -> renderParameter(node)
+ NodeKind.Constructor,
+ NodeKind.Function -> renderFunction(node)
+ NodeKind.Property -> renderProperty(node)
+ else -> "${node.kind}: ${node.name}"
+ })
+ }
+ }
+
+ override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? = null
+
+
+ override fun renderModifier(block: ContentBlock, node: DocumentationNode, renderMode: RenderMode, nowrap: Boolean) {
+ when (node.name) {
+ "open", "internal" -> {
+ }
+ else -> super.renderModifier(block, node, renderMode, nowrap)
+ }
+ }
+
+ fun getArrayElementType(node: DocumentationNode): DocumentationNode? = when (node.qualifiedName()) {
+ "kotlin.Array" ->
+ node.details(NodeKind.Type).singleOrNull()?.let { et -> getArrayElementType(et) ?: et }
+ ?: DocumentationNode("Object", node.content, NodeKind.ExternalClass)
+
+ "kotlin.IntArray", "kotlin.LongArray", "kotlin.ShortArray", "kotlin.ByteArray",
+ "kotlin.CharArray", "kotlin.DoubleArray", "kotlin.FloatArray", "kotlin.BooleanArray" ->
+ DocumentationNode(node.name.removeSuffix("Array").toLowerCase(), node.content, NodeKind.Type)
+
+ else -> null
+ }
+
+ fun getArrayDimension(node: DocumentationNode): Int = when (node.qualifiedName()) {
+ "kotlin.Array" ->
+ 1 + (node.details(NodeKind.Type).singleOrNull()?.let { getArrayDimension(it) } ?: 0)
+
+ "kotlin.IntArray", "kotlin.LongArray", "kotlin.ShortArray", "kotlin.ByteArray",
+ "kotlin.CharArray", "kotlin.DoubleArray", "kotlin.FloatArray", "kotlin.BooleanArray" ->
+ 1
+ else -> 0
+ }
+
+ fun ContentBlock.renderType(node: DocumentationNode) {
+ when (node.name) {
+ "Unit" -> identifier("void")
+ "Int" -> identifier("int")
+ "Long" -> identifier("long")
+ "Double" -> identifier("double")
+ "Float" -> identifier("float")
+ "Char" -> identifier("char")
+ "Boolean" -> identifier("bool")
+ // TODO: render arrays
+ else -> renderLinked(this, node) {
+ identifier(node.name)
+ }
+ }
+ }
+
+ private fun ContentBlock.renderTypeParameter(node: DocumentationNode) {
+ val constraints = node.details(NodeKind.UpperBound)
+ if (constraints.none())
+ identifier(node.name)
+ else {
+ identifier(node.name)
+ text(" ")
+ keyword("extends")
+ text(" ")
+ constraints.forEach { renderType(node) }
+ }
+ }
+
+ private fun ContentBlock.renderParameter(node: DocumentationNode) {
+ renderType(node.detail(NodeKind.Type))
+ text(" ")
+ identifier(node.name)
+ }
+
+ private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode) {
+ val typeParameters = node.details(NodeKind.TypeParameter)
+ if (typeParameters.any()) {
+ symbol("<")
+ renderList(typeParameters, noWrap = true) {
+ renderTypeParameter(it)
+ }
+ symbol(">")
+ text(" ")
+ }
+ }
+
+// private fun renderModifiersForNode(node: DocumentationNode): String {
+// val modifiers = node.details(NodeKind.Modifier).map { renderModifier(it) }.filter { it != "" }
+// if (modifiers.none())
+// return ""
+// return modifiers.joinToString(" ", postfix = " ")
+// }
+
+ private fun ContentBlock.renderClassKind(node: DocumentationNode) {
+ when (node.kind) {
+ NodeKind.Interface -> {
+ keyword("interface")
+ }
+ NodeKind.EnumItem -> {
+ keyword("enum value")
+ }
+ NodeKind.Enum -> {
+ keyword("enum")
+ }
+ NodeKind.Class, NodeKind.Exception, NodeKind.Object -> {
+ keyword("class")
+ }
+ else -> throw IllegalArgumentException("Node $node is not a class-like object")
+ }
+ text(" ")
+ }
+
+ private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) {
+ renderModifiersForNode(node, renderMode)
+ renderClassKind(node)
+
+ identifier(node.name)
+ renderTypeParametersForNode(node)
+ }
+
+ private fun ContentBlock.renderParameters(nodes: List<DocumentationNode>) {
+ renderList(nodes) {
+ renderParameter(it)
+ }
+ }
+
+ private fun ContentBlock.renderFunction(node: DocumentationNode) {
+ when (node.kind) {
+ NodeKind.Constructor -> identifier(node.owner?.name ?: "")
+ NodeKind.Function -> {
+ renderTypeParametersForNode(node)
+ renderType(node.detail(NodeKind.Type))
+ text(" ")
+ identifier(node.name)
+
+ }
+ else -> throw IllegalArgumentException("Node $node is not a function-like object")
+ }
+
+ val receiver = node.details(NodeKind.Receiver).singleOrNull()
+ symbol("(")
+ if (receiver != null)
+ renderParameters(listOf(receiver) + node.details(NodeKind.Parameter))
+ else
+ renderParameters(node.details(NodeKind.Parameter))
+
+ symbol(")")
+ }
+
+ private fun ContentBlock.renderProperty(node: DocumentationNode) {
+
+ when (node.kind) {
+ NodeKind.Property -> {
+ keyword("val")
+ text(" ")
+ }
+ else -> throw IllegalArgumentException("Node $node is not a property")
+ }
+ renderTypeParametersForNode(node)
+ val receiver = node.details(NodeKind.Receiver).singleOrNull()
+ if (receiver != null) {
+ renderType(receiver.detail(NodeKind.Type))
+ symbol(".")
+ }
+
+ identifier(node.name)
+ symbol(":")
+ text(" ")
+ renderType(node.detail(NodeKind.Type))
+
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Locations/FoldersLocationService.kt b/core/src/main/kotlin/Locations/FoldersLocationService.kt
deleted file mode 100644
index 83e1cf6a..00000000
--- a/core/src/main/kotlin/Locations/FoldersLocationService.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.jetbrains.dokka
-
-import com.google.inject.Inject
-import com.google.inject.name.Named
-import java.io.File
-
-class FoldersLocationService @Inject constructor(@Named("outputDir") val rootFile: File, val extension: String) : FileLocationService {
- constructor(root: String): this(File(root), "")
-
- override val root: Location
- get() = FileLocation(rootFile)
-
- override fun withExtension(newExtension: String): FileLocationService {
- return if (extension.isEmpty()) FoldersLocationService(rootFile, newExtension) else this
- }
-
- override fun location(qualifiedName: List<String>, hasMembers: Boolean): FileLocation {
- return FileLocation(File(rootFile, relativePathToNode(qualifiedName, hasMembers)).appendExtension(extension))
- }
-}
-
-fun relativePathToNode(qualifiedName: List<String>, hasMembers: Boolean): String {
- val parts = qualifiedName.map { identifierToFilename(it) }.filterNot { it.isEmpty() }
- return if (!hasMembers) {
- // leaf node, use file in owner's folder
- parts.joinToString("/")
- } else {
- parts.joinToString("/") + (if (parts.none()) "" else "/") + "index"
- }
-}
diff --git a/core/src/main/kotlin/Locations/Location.kt b/core/src/main/kotlin/Locations/Location.kt
new file mode 100644
index 00000000..4cb0ac39
--- /dev/null
+++ b/core/src/main/kotlin/Locations/Location.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka
+
+import java.io.File
+
+interface Location {
+ val path: String get
+ fun relativePathTo(other: Location, anchor: String? = null): String
+}
+
+/**
+ * Represents locations in the documentation in the form of [path](File).
+ *
+ * $file: [File] for this location
+ * $path: [String] representing path of this location
+ */
+data class FileLocation(val file: File) : Location {
+ override val path: String
+ get() = file.path
+
+ override fun relativePathTo(other: Location, anchor: String?): String {
+ if (other !is FileLocation) {
+ throw IllegalArgumentException("$other is not a FileLocation")
+ }
+ if (file.path.substringBeforeLast(".") == other.file.path.substringBeforeLast(".") && anchor == null) {
+ return "./${file.name}"
+ }
+ val ownerFolder = file.parentFile!!
+ val relativePath = ownerFolder.toPath().relativize(other.file.toPath()).toString().replace(File.separatorChar, '/')
+ return if (anchor == null) relativePath else relativePath + "#" + anchor
+ }
+}
+
+fun relativePathToNode(qualifiedName: List<String>, hasMembers: Boolean): String {
+ val parts = qualifiedName.map { identifierToFilename(it) }.filterNot { it.isEmpty() }
+ return if (!hasMembers) {
+ // leaf node, use file in owner's folder
+ parts.joinToString("/")
+ } else {
+ parts.joinToString("/") + (if (parts.none()) "" else "/") + "index"
+ }
+}
+
+
+fun relativePathToNode(node: DocumentationNode) = relativePathToNode(node.path.map { it.name }, node.members.any())
+
+fun identifierToFilename(path: String): String {
+ val escaped = path.replace('<', '-').replace('>', '-')
+ val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() }
+ return if (lowercase == "index") "--index--" else lowercase
+}
+
+fun NodeLocationAwareGenerator.relativePathToLocation(owner: DocumentationNode, node: DocumentationNode): String {
+ return location(owner).relativePathTo(location(node), null)
+}
+
+fun NodeLocationAwareGenerator.relativePathToRoot(from: Location): File {
+ val file = File(from.path).parentFile
+ return root.relativeTo(file)
+}
+
+fun File.toUnixString() = toString().replace(File.separatorChar, '/')
diff --git a/core/src/main/kotlin/Locations/LocationService.kt b/core/src/main/kotlin/Locations/LocationService.kt
deleted file mode 100644
index a51ef8d4..00000000
--- a/core/src/main/kotlin/Locations/LocationService.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.jetbrains.dokka
-
-import java.io.File
-
-interface Location {
- val path: String get
- fun relativePathTo(other: Location, anchor: String? = null): String
-}
-
-/**
- * Represents locations in the documentation in the form of [path](File).
- *
- * Locations are provided by [LocationService.location] function.
- *
- * $file: [File] for this location
- * $path: [String] representing path of this location
- */
-data class FileLocation(val file: File): Location {
- override val path : String
- get() = file.path
-
- override fun relativePathTo(other: Location, anchor: String?): String {
- if (other !is FileLocation) {
- throw IllegalArgumentException("$other is not a FileLocation")
- }
- if (file.path.substringBeforeLast(".") == other.file.path.substringBeforeLast(".") && anchor == null) {
- return "./${file.name}"
- }
- val ownerFolder = file.parentFile!!
- val relativePath = ownerFolder.toPath().relativize(other.file.toPath()).toString().replace(File.separatorChar, '/')
- return if (anchor == null) relativePath else relativePath + "#" + anchor
- }
-}
-
-/**
- * Provides means of retrieving locations for [DocumentationNode](documentation nodes)
- *
- * `LocationService` determines where documentation for particular node should be generated
- *
- * * [FoldersLocationService] – represent packages and types as folders, members as files in those folders.
- * * [SingleFolderLocationService] – all documentation is generated into single folder using fully qualified names
- * for file names.
- */
-interface LocationService {
- fun withExtension(newExtension: String) = this
-
- fun location(node: DocumentationNode): Location = location(node.path.map { it.name }, node.members.any())
-
- /**
- * Calculates a location corresponding to the specified [qualifiedName].
- * @param hasMembers if true, the node for which the location is calculated has member nodes.
- */
- fun location(qualifiedName: List<String>, hasMembers: Boolean): Location
-
- val root: Location
-}
-
-
-interface FileLocationService: LocationService {
- override fun withExtension(newExtension: String): FileLocationService = this
-
- override fun location(node: DocumentationNode): FileLocation = location(node.path.map { it.name }, node.members.any())
- override fun location(qualifiedName: List<String>, hasMembers: Boolean): FileLocation
-}
-
-
-fun identifierToFilename(path: String): String {
- val escaped = path.replace('<', '-').replace('>', '-')
- val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() }
- return if (lowercase == "index") "--index--" else lowercase
-}
-
-/**
- * Returns relative location between two nodes. Used for relative links in documentation.
- */
-fun LocationService.relativePathToLocation(owner: DocumentationNode, node: DocumentationNode): String {
- return location(owner).relativePathTo(location(node), null)
-}
diff --git a/core/src/main/kotlin/Locations/SingleFolderLocationService.kt b/core/src/main/kotlin/Locations/SingleFolderLocationService.kt
deleted file mode 100644
index 1b4fdc28..00000000
--- a/core/src/main/kotlin/Locations/SingleFolderLocationService.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.jetbrains.dokka
-
-import com.google.inject.Inject
-import com.google.inject.name.Named
-import java.io.File
-
-class SingleFolderLocationService @Inject constructor(@Named("outputDir") val rootFile: File, val extension: String) : FileLocationService {
- constructor(root: String): this(File(root), "")
-
- override fun withExtension(newExtension: String): FileLocationService =
- SingleFolderLocationService(rootFile, newExtension)
-
- override fun location(qualifiedName: List<String>, hasMembers: Boolean): FileLocation {
- val filename = qualifiedName.map { identifierToFilename(it) }.joinToString("-")
- return FileLocation(File(rootFile, filename).appendExtension(extension))
- }
-
- override val root: Location
- get() = FileLocation(rootFile)
-} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/Content.kt b/core/src/main/kotlin/Model/Content.kt
index 1f5bbc83..7c776bdb 100644
--- a/core/src/main/kotlin/Model/Content.kt
+++ b/core/src/main/kotlin/Model/Content.kt
@@ -9,7 +9,7 @@ object ContentEmpty : ContentNode {
}
open class ContentBlock() : ContentNode {
- val children = arrayListOf<ContentNode>()
+ open val children = arrayListOf<ContentNode>()
fun append(node: ContentNode) {
children.add(node)
@@ -27,6 +27,34 @@ open class ContentBlock() : ContentNode {
get() = children.sumBy { it.textLength }
}
+class NodeRenderContent(
+ val node: DocumentationNode,
+ val mode: LanguageService.RenderMode
+): ContentNode {
+ override val textLength: Int
+ get() = 0 //TODO: Clarify?
+}
+
+class LazyContentBlock(private val fillChildren: (ContentBlock) -> Unit) : ContentBlock() {
+ private var computed = false
+ override val children: ArrayList<ContentNode>
+ get() {
+ if (!computed) {
+ computed = true
+ fillChildren(this)
+ }
+ return super.children
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is LazyContentBlock && other.fillChildren == fillChildren && super.equals(other)
+ }
+
+ override fun hashCode(): Int {
+ return super.hashCode() + 31 * fillChildren.hashCode()
+ }
+}
+
enum class IdentifierKind {
TypeName,
ParameterName,
@@ -120,6 +148,9 @@ class ContentExternalLink(val href : String) : ContentBlock() {
children.hashCode() * 31 + href.hashCode()
}
+data class ContentBookmark(val name: String): ContentBlock()
+data class ContentLocalLink(val href: String) : ContentBlock()
+
class ContentUnorderedList() : ContentBlock()
class ContentOrderedList() : ContentBlock()
class ContentListItem() : ContentBlock()
diff --git a/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt b/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt
deleted file mode 100644
index 85584e3c..00000000
--- a/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.jetbrains.dokka.Model
-
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-
-interface DescriptorSignatureProvider {
- fun signature(forDesc: DeclarationDescriptor): String
-} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index da85cabf..f2b3a937 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -48,6 +48,7 @@ enum class NodeKind {
Signature,
ExternalLink,
+ QualifiedName,
Platform,
AllTypes,
@@ -62,7 +63,7 @@ enum class NodeKind {
companion object {
val classLike = setOf(Class, Interface, Enum, AnnotationClass, Exception, Object, TypeAlias)
- val memberLike = setOf(Function, Property, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem)
+ val memberLike = setOf(Function, Property, Field, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem)
}
}
@@ -75,7 +76,11 @@ open class DocumentationNode(val name: String,
var content: Content = content
private set
- val summary: ContentNode get() = content.summary
+ val summary: ContentNode get() = when (kind) {
+ NodeKind.GroupNode -> this.origins.first().summary
+ else -> content.summary
+ }
+
val owner: DocumentationNode?
get() = references(RefKind.Owner).singleOrNull()?.to
@@ -83,8 +88,13 @@ open class DocumentationNode(val name: String,
get() = references(RefKind.Detail).map { it.to }
val members: List<DocumentationNode>
get() = references(RefKind.Member).map { it.to }
+ val origins: List<DocumentationNode>
+ get() = references(RefKind.Origin).map { it.to }
+
val inheritedMembers: List<DocumentationNode>
get() = references(RefKind.InheritedMember).map { it.to }
+ val allInheritedMembers: List<DocumentationNode>
+ get() = recursiveInheritedMembers()
val inheritedCompanionObjectMembers: List<DocumentationNode>
get() = references(RefKind.InheritedCompanionObjectMember).map { it.to }
val extensions: List<DocumentationNode>
@@ -103,12 +113,38 @@ open class DocumentationNode(val name: String,
get() = references(RefKind.Deprecation).singleOrNull()?.to
val platforms: List<String>
get() = references(RefKind.Platform).map { it.to.name }
+ val externalType: DocumentationNode?
+ get() = references(RefKind.ExternalType).map { it.to }.firstOrNull()
+
+ val supertypes: List<DocumentationNode>
+ get() = details(NodeKind.Supertype)
+
+ val superclassType: DocumentationNode?
+ get() = when (kind) {
+ NodeKind.Supertype -> {
+ (links + listOfNotNull(externalType)).firstOrNull { it.kind in NodeKind.classLike }?.superclassType
+ }
+ NodeKind.Interface -> null
+ in NodeKind.classLike -> supertypes.firstOrNull {
+ (it.links + listOfNotNull(it.externalType)).any { it.isSuperclassFor(this) }
+ }
+ else -> null
+ }
+
+ val superclassTypeSequence: Sequence<DocumentationNode>
+ get() = generateSequence(superclassType) {
+ it.superclassType
+ }
// TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice
fun addReferenceTo(to: DocumentationNode, kind: RefKind) {
references.add(DocumentationReference(this, to, kind))
}
+ fun addReference(reference: DocumentationReference) {
+ references.add(reference)
+ }
+
fun dropReferences(predicate: (DocumentationReference) -> Boolean) {
references.removeAll(predicate)
}
@@ -123,7 +159,6 @@ open class DocumentationNode(val name: String,
}
(content as MutableContent).body()
}
-
fun details(kind: NodeKind): List<DocumentationNode> = details.filter { it.kind == kind }
fun members(kind: NodeKind): List<DocumentationNode> = members.filter { it.kind == kind }
fun inheritedMembers(kind: NodeKind): List<DocumentationNode> = inheritedMembers.filter { it.kind == kind }
@@ -135,6 +170,7 @@ open class DocumentationNode(val name: String,
fun member(kind: NodeKind): DocumentationNode = members.filter { it.kind == kind }.single()
fun link(kind: NodeKind): DocumentationNode = links.filter { it.kind == kind }.single()
+
fun references(kind: RefKind): List<DocumentationReference> = references.filter { it.kind == kind }
fun allReferences(): Set<DocumentationReference> = references
@@ -143,9 +179,9 @@ open class DocumentationNode(val name: String,
}
}
-class DocumentationModule(name: String, content: Content = Content.Empty)
+class DocumentationModule(name: String, content: Content = Content.Empty, val nodeRefGraph: NodeReferenceGraph = NodeReferenceGraph())
: DocumentationNode(name, content, NodeKind.Module) {
- val nodeRefGraph = NodeReferenceGraph()
+
}
val DocumentationNode.path: List<DocumentationNode>
@@ -154,16 +190,17 @@ val DocumentationNode.path: List<DocumentationNode>
return parent.path + this
}
-fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode {
- val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName }
+fun findOrCreatePackageNode(module: DocumentationNode?, packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode {
+ val existingNode = refGraph.lookup(packageName)
if (existingNode != null) {
return existingNode
}
val newNode = DocumentationNode(packageName,
packageContent.getOrElse(packageName) { Content.Empty },
NodeKind.Package)
- append(newNode, RefKind.Member)
+
refGraph.register(packageName, newNode)
+ module?.append(newNode, RefKind.Member)
return newNode
}
@@ -173,7 +210,9 @@ fun DocumentationNode.append(child: DocumentationNode, kind: RefKind) {
RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner)
RefKind.Member -> child.addReferenceTo(this, RefKind.Owner)
RefKind.Owner -> child.addReferenceTo(this, RefKind.Member)
- else -> { /* Do not add any links back for other types */ }
+ RefKind.Origin -> child.addReferenceTo(this, RefKind.Owner)
+ else -> { /* Do not add any links back for other types */
+ }
}
}
@@ -186,8 +225,37 @@ fun DocumentationNode.appendTextNode(text: String,
fun DocumentationNode.qualifiedName(): String {
if (kind == NodeKind.Type) {
return qualifiedNameFromType()
+ } else if (kind == NodeKind.Package) {
+ return name
}
return path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".")
}
fun DocumentationNode.simpleName() = name.substringAfterLast('.')
+
+private fun DocumentationNode.recursiveInheritedMembers(): List<DocumentationNode> {
+ val allInheritedMembers = mutableListOf<DocumentationNode>()
+ recursiveInheritedMembers(allInheritedMembers)
+ return allInheritedMembers
+}
+
+private fun DocumentationNode.recursiveInheritedMembers(allInheritedMembers: MutableList<DocumentationNode>) {
+ allInheritedMembers.addAll(inheritedMembers)
+ System.out.println(allInheritedMembers.size)
+ inheritedMembers.groupBy { it.owner!! } .forEach { (node, _) ->
+ node.recursiveInheritedMembers(allInheritedMembers)
+ }
+}
+
+private fun DocumentationNode.isSuperclassFor(node: DocumentationNode): Boolean {
+ return when(node.kind) {
+ NodeKind.Object, NodeKind.Class, NodeKind.Enum -> kind == NodeKind.Class
+ NodeKind.Exception -> kind == NodeKind.Class || kind == NodeKind.Exception
+ else -> false
+ }
+}
+
+fun DocumentationNode.classNodeNameWithOuterClass(): String {
+ assert(kind in NodeKind.classLike)
+ return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/DocumentationReference.kt b/core/src/main/kotlin/Model/DocumentationReference.kt
index a968f400..e10796d2 100644
--- a/core/src/main/kotlin/Model/DocumentationReference.kt
+++ b/core/src/main/kotlin/Model/DocumentationReference.kt
@@ -18,42 +18,83 @@ enum class RefKind {
HiddenAnnotation,
Deprecation,
TopLevelPage,
- Platform
+ Platform,
+ ExternalType,
+ Origin
}
data class DocumentationReference(val from: DocumentationNode, val to: DocumentationNode, val kind: RefKind) {
}
-class PendingDocumentationReference(val lazyNodeFrom: () -> DocumentationNode?,
- val lazyNodeTo: () -> DocumentationNode?,
+sealed class NodeResolver {
+ abstract fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode?
+ class BySignature(var signature: String) : NodeResolver() {
+ override fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode? {
+ return nodeRephGraph.lookup(signature)
+ }
+ }
+
+ class Exact(var exactNode: DocumentationNode) : NodeResolver() {
+ override fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode? {
+ return exactNode
+ }
+ }
+}
+
+class PendingDocumentationReference(val lazyNodeFrom: NodeResolver,
+ val lazyNodeTo: NodeResolver,
val kind: RefKind) {
- fun resolve() {
- val fromNode = lazyNodeFrom()
- val toNode = lazyNodeTo()
+ fun resolve(nodeRephGraph: NodeReferenceGraph) {
+ val fromNode = lazyNodeFrom.resolve(nodeRephGraph)
+ val toNode = lazyNodeTo.resolve(nodeRephGraph)
if (fromNode != null && toNode != null) {
fromNode.addReferenceTo(toNode, kind)
}
}
}
-class NodeReferenceGraph() {
+class NodeReferenceGraph {
private val nodeMap = hashMapOf<String, DocumentationNode>()
+ val nodeMapView: Map<String, DocumentationNode>
+ get() = HashMap(nodeMap)
+
val references = arrayListOf<PendingDocumentationReference>()
fun register(signature: String, node: DocumentationNode) {
- nodeMap.put(signature, node)
+ nodeMap[signature] = node
}
fun link(fromNode: DocumentationNode, toSignature: String, kind: RefKind) {
- references.add(PendingDocumentationReference({ -> fromNode}, { -> nodeMap[toSignature]}, kind))
+ references.add(
+ PendingDocumentationReference(
+ NodeResolver.Exact(fromNode),
+ NodeResolver.BySignature(toSignature),
+ kind
+ ))
}
fun link(fromSignature: String, toNode: DocumentationNode, kind: RefKind) {
- references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> toNode}, kind))
+ references.add(
+ PendingDocumentationReference(
+ NodeResolver.BySignature(fromSignature),
+ NodeResolver.Exact(toNode),
+ kind
+ )
+ )
}
fun link(fromSignature: String, toSignature: String, kind: RefKind) {
- references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> nodeMap[toSignature]}, kind))
+ references.add(
+ PendingDocumentationReference(
+ NodeResolver.BySignature(fromSignature),
+ NodeResolver.BySignature(toSignature),
+ kind
+ )
+ )
+ }
+
+ fun addReference(reference: PendingDocumentationReference) {
+ references.add(reference)
}
fun lookup(signature: String) = nodeMap[signature]
@@ -61,13 +102,13 @@ class NodeReferenceGraph() {
fun lookupOrWarn(signature: String, logger: DokkaLogger): DocumentationNode? {
val result = nodeMap[signature]
if (result == null) {
- logger.warn("Can't find node by signature $signature")
+ logger.warn("Can't find node by signature `$signature`")
}
return result
}
fun resolveReferences() {
- references.forEach { it.resolve() }
+ references.forEach { it.resolve(this) }
}
}
diff --git a/core/src/main/kotlin/Model/ElementSignatureProvider.kt b/core/src/main/kotlin/Model/ElementSignatureProvider.kt
new file mode 100644
index 00000000..e8fdde6e
--- /dev/null
+++ b/core/src/main/kotlin/Model/ElementSignatureProvider.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+
+interface ElementSignatureProvider {
+ fun signature(forDesc: DeclarationDescriptor): String
+ fun signature(forPsi: PsiElement): String
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/PackageDocs.kt b/core/src/main/kotlin/Model/PackageDocs.kt
index 1f6bdcb9..5b628914 100644
--- a/core/src/main/kotlin/Model/PackageDocs.kt
+++ b/core/src/main/kotlin/Model/PackageDocs.kt
@@ -2,16 +2,25 @@ package org.jetbrains.dokka
import com.google.inject.Inject
import com.google.inject.Singleton
+import com.intellij.ide.highlighter.JavaFileType
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiFileFactory
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.LocalTimeCounter
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.parser.LinkMap
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import java.io.File
@Singleton
class PackageDocs
@Inject constructor(val linkResolver: DeclarationLinkResolver?,
- val logger: DokkaLogger)
+ val logger: DokkaLogger,
+ val environment: KotlinCoreEnvironment,
+ val refGraph: NodeReferenceGraph,
+ val elementSignatureProvider: ElementSignatureProvider)
{
val moduleContent: MutableContent = MutableContent()
private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
@@ -40,6 +49,64 @@ class PackageDocs
}
}
+ private fun parseHtmlAsJavadoc(text: String, packageName: String, file: File) {
+ val javadocText = text
+ .replace("*/", "*&#47;")
+ .removeSurrounding("<html>", "</html>", true).trim()
+ .removeSurrounding("<body>", "</body>", true)
+ .lineSequence()
+ .map { "* $it" }
+ .joinToString (separator = "\n", prefix = "/**\n", postfix = "\n*/")
+ parseJavadoc(javadocText, packageName, file)
+ }
+
+ private fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence, ignoringCase: Boolean = false): CharSequence {
+ if ((length >= prefix.length + suffix.length) && startsWith(prefix, ignoringCase) && endsWith(suffix, ignoringCase)) {
+ return subSequence(prefix.length, length - suffix.length)
+ }
+ return subSequence(0, length)
+ }
+
+
+ private fun parseJavadoc(text: String, packageName: String, file: File) {
+
+ val psiFileFactory = PsiFileFactory.getInstance(environment.project)
+ val psiFile = psiFileFactory.createFileFromText(
+ file.nameWithoutExtension + ".java",
+ JavaFileType.INSTANCE,
+ "package $packageName; $text\npublic class C {}",
+ LocalTimeCounter.currentTime(),
+ false,
+ true
+ )
+
+ val psiClass = PsiTreeUtil.getChildOfType(psiFile, PsiClass::class.java)!!
+ val parser = JavadocParser(refGraph, logger, elementSignatureProvider, linkResolver?.externalDocumentationLinkResolver!!)
+ findOrCreatePackageContent(packageName).apply {
+ val content = parser.parseDocumentation(psiClass).content
+ children.addAll(content.children)
+ content.sections.forEach {
+ addSection(it.tag, it.subjectName).children.addAll(it.children)
+ }
+ }
+ }
+
+
+ fun parseJava(fileName: String, packageName: String) {
+ val file = File(fileName)
+ if (file.exists()) {
+ val text = file.readText()
+
+ val trimmedText = text.trim()
+
+ if (trimmedText.startsWith("/**")) {
+ parseJavadoc(text, packageName, file)
+ } else if (trimmedText.toLowerCase().startsWith("<html>")) {
+ parseHtmlAsJavadoc(trimmedText, packageName, file)
+ }
+ }
+ }
+
private fun findTargetContent(heading: String): MutableContent {
if (heading.startsWith("Module") || heading.startsWith("module")) {
return moduleContent
diff --git a/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt b/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt
index 116a5c02..f3f45c3f 100644
--- a/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt
+++ b/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
open class DefaultSampleProcessingService
-@Inject constructor(val options: DocumentationOptions,
+@Inject constructor(val configuration: DokkaConfiguration,
val logger: DokkaLogger,
val resolutionFacade: DokkaResolutionFacade)
: SampleProcessingService {
diff --git a/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt b/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt
index f928b44f..4525e9d9 100644
--- a/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt
+++ b/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt
@@ -18,10 +18,10 @@ import java.io.StringWriter
open class KotlinWebsiteSampleProcessingService
-@Inject constructor(options: DocumentationOptions,
+@Inject constructor(dokkaConfiguration: DokkaConfiguration,
logger: DokkaLogger,
resolutionFacade: DokkaResolutionFacade)
- : DefaultSampleProcessingService(options, logger, resolutionFacade) {
+ : DefaultSampleProcessingService(dokkaConfiguration, logger, resolutionFacade) {
private class SampleBuilder : KtTreeVisitorVoid() {
val builder = StringBuilder()
diff --git a/core/src/main/kotlin/Utilities/DokkaModules.kt b/core/src/main/kotlin/Utilities/DokkaModules.kt
index dfb114ec..c2e652b6 100644
--- a/core/src/main/kotlin/Utilities/DokkaModules.kt
+++ b/core/src/main/kotlin/Utilities/DokkaModules.kt
@@ -1,90 +1,63 @@
package org.jetbrains.dokka.Utilities
-import com.google.inject.Binder
-import com.google.inject.Module
-import com.google.inject.Provider
-import com.google.inject.TypeLiteral
+import com.google.inject.*
+import com.google.inject.binder.AnnotatedBindingBuilder
import com.google.inject.name.Names
import org.jetbrains.dokka.*
import org.jetbrains.dokka.Formats.FormatDescriptor
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.Samples.SampleProcessingService
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import java.io.File
+import kotlin.reflect.KClass
const val impliedPlatformsName = "impliedPlatforms"
+class DokkaRunModule(val configuration: DokkaConfiguration) : Module {
+ override fun configure(binder: Binder) {
+ binder.bind<DokkaConfiguration>().toInstance(configuration)
+ binder.bind(StringListType).annotatedWith(Names.named(impliedPlatformsName)).toInstance(configuration.impliedPlatforms)
+
+ binder.bind(File::class.java).annotatedWith(Names.named("outputDir")).toInstance(File(configuration.outputDir))
+ }
+
+}
+
class DokkaAnalysisModule(val environment: AnalysisEnvironment,
- val options: DocumentationOptions,
+ val configuration: DokkaConfiguration,
val defaultPlatformsProvider: DefaultPlatformsProvider,
val nodeReferenceGraph: NodeReferenceGraph,
+ val passConfiguration: DokkaConfiguration.PassConfiguration,
val logger: DokkaLogger) : Module {
override fun configure(binder: Binder) {
binder.bind<DokkaLogger>().toInstance(logger)
- val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", options.outputFormat)
- binder.bind<DescriptorSignatureProvider>().to(descriptor.descriptorSignatureProvider.java)
- binder.registerCategory<LanguageService>("language")
- binder.bind<PackageDocumentationBuilder>().to(descriptor.packageDocumentationBuilderClass.java)
- binder.bind<JavaDocumentationBuilder>().to(descriptor.javaDocumentationBuilderClass.java)
- binder.bind<SampleProcessingService>().to(descriptor.sampleProcessingService.java)
-
val coreEnvironment = environment.createCoreEnvironment()
binder.bind<KotlinCoreEnvironment>().toInstance(coreEnvironment)
- val dokkaResolutionFacade = environment.createResolutionFacade(coreEnvironment)
+ val (dokkaResolutionFacade, libraryResolutionFacade) = environment.createResolutionFacade(coreEnvironment)
binder.bind<DokkaResolutionFacade>().toInstance(dokkaResolutionFacade)
+ binder.bind<DokkaResolutionFacade>().annotatedWith(Names.named("libraryResolutionFacade")).toInstance(libraryResolutionFacade)
- binder.bind<DocumentationOptions>().toInstance(options)
+ binder.bind<DokkaConfiguration.PassConfiguration>().toInstance(passConfiguration)
binder.bind<DefaultPlatformsProvider>().toInstance(defaultPlatformsProvider)
binder.bind<NodeReferenceGraph>().toInstance(nodeReferenceGraph)
+
+ val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", configuration.format)
+ descriptor.configureAnalysis(binder)
}
}
object StringListType : TypeLiteral<@JvmSuppressWildcards List<String>>()
-class DokkaOutputModule(val options: DocumentationOptions,
+class DokkaOutputModule(val configuration: DokkaConfiguration,
val logger: DokkaLogger) : Module {
override fun configure(binder: Binder) {
- binder.bind(LanguageService::class.java).to(KotlinLanguageService::class.java)
-
- binder.bind(HtmlTemplateService::class.java).toProvider(object : Provider<HtmlTemplateService> {
- override fun get(): HtmlTemplateService = HtmlTemplateService.default("style.css")
- })
-
- binder.bind(File::class.java).annotatedWith(Names.named("outputDir")).toInstance(File(options.outputDir))
-
- binder.bindNameAnnotated<LocationService, SingleFolderLocationService>("singleFolder")
- binder.bindNameAnnotated<FileLocationService, SingleFolderLocationService>("singleFolder")
- binder.bindNameAnnotated<LocationService, FoldersLocationService>("folders")
- binder.bindNameAnnotated<FileLocationService, FoldersLocationService>("folders")
-
- // defaults
- binder.bind(LocationService::class.java).to(FoldersLocationService::class.java)
- binder.bind(FileLocationService::class.java).to(FoldersLocationService::class.java)
-
- binder.registerCategory<OutlineFormatService>("outline")
- binder.registerCategory<FormatService>("format")
- binder.registerCategory<Generator>("generator")
-
- val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", options.outputFormat)
-
- descriptor.outlineServiceClass?.let { clazz ->
- binder.bind(OutlineFormatService::class.java).to(clazz.java)
- }
- descriptor.formatServiceClass?.let { clazz ->
- binder.bind(FormatService::class.java).to(clazz.java)
- }
-
- binder.bind<Generator>().to(descriptor.generatorServiceClass.java)
+ binder.bind<DokkaLogger>().toInstance(logger)
- descriptor.packageListServiceClass?.let { binder.bind<PackageListService>().to(it.java) }
+ val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", configuration.format)
- binder.bind<DocumentationOptions>().toInstance(options)
- binder.bind<DokkaLogger>().toInstance(logger)
- binder.bind(StringListType).annotatedWith(Names.named(impliedPlatformsName)).toInstance(options.impliedPlatforms)
+ descriptor.configureOutput(binder)
}
}
@@ -100,4 +73,11 @@ private inline fun <reified Base : Any, reified T : Base> Binder.bindNameAnnotat
}
-inline fun <reified T: Any> Binder.bind() = bind(T::class.java)
+inline fun <reified T: Any> Binder.bind(): AnnotatedBindingBuilder<T> = bind(T::class.java)
+
+inline fun <reified T: Any> Binder.lazyBind(): Lazy<AnnotatedBindingBuilder<T>> = lazy { bind(T::class.java) }
+
+inline infix fun <reified T: Any, TKClass: KClass<out T>> Lazy<AnnotatedBindingBuilder<T>>.toOptional(kClass: TKClass?) =
+ kClass?.let { value toType it }
+
+inline infix fun <reified T: Any, TKClass: KClass<out T>> AnnotatedBindingBuilder<T>.toType(kClass: TKClass) = to(kClass.java)
diff --git a/core/src/main/kotlin/Utilities/Html.kt b/core/src/main/kotlin/Utilities/Html.kt
index a5a93d9e..de1ce1a5 100644
--- a/core/src/main/kotlin/Utilities/Html.kt
+++ b/core/src/main/kotlin/Utilities/Html.kt
@@ -1,8 +1,12 @@
package org.jetbrains.dokka
+import java.net.URLEncoder
+
/**
* Replaces symbols reserved in HTML with their respective entities.
* Replaces & with &amp;, < with &lt; and > with &gt;
*/
fun String.htmlEscape(): String = replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+
+fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8") \ No newline at end of file
diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt
index 71bfd21b..fca08f38 100644
--- a/core/src/main/kotlin/Utilities/ServiceLocator.kt
+++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt
@@ -14,12 +14,21 @@ class ServiceLookupException(message: String) : Exception(message)
object ServiceLocator {
fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
val descriptor = lookupDescriptor(category, implementationName)
+ return lookup(clazz, descriptor)
+ }
+
+ fun <T : Any> lookup(
+ clazz: Class<T>,
+ descriptor: ServiceDescriptor
+ ): T {
val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
val constructor = loadedClass.constructors
- .filter { it.parameterTypes.isEmpty() }
- .firstOrNull() ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
+ .filter { it.parameterTypes.isEmpty() }
+ .firstOrNull()
+ ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
- val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
+ val implementationRawType: Any =
+ if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
if (!clazz.isInstance(implementationRawType)) {
throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
@@ -61,7 +70,7 @@ object ServiceLocator {
"jar" -> {
val file = JarFile(URL(it.file.substringBefore("!")).toFile())
try {
- val jarPath = it.file.substringAfterLast("!").removePrefix("/")
+ val jarPath = it.file.substringAfterLast("!").removeSurrounding("/")
file.entries()
.asSequence()
.filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" }
@@ -79,6 +88,7 @@ object ServiceLocator {
}
inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
+inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc)
private val ZipEntry.fileName: String
get() = name.substringAfterLast("/", name)
diff --git a/core/src/main/kotlin/Utilities/Uri.kt b/core/src/main/kotlin/Utilities/Uri.kt
new file mode 100644
index 00000000..9827c624
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/Uri.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka
+
+import java.net.URI
+
+
+fun URI.relativeTo(uri: URI): URI {
+ // Normalize paths to remove . and .. segments
+ val base = uri.normalize()
+ val child = this.normalize()
+
+ fun StringBuilder.appendRelativePath() {
+ // Split paths into segments
+ var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
+ val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
+
+ // Discard trailing segment of base path
+ if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
+ bParts = bParts.dropLast(1)
+ }
+
+ // Compute common prefix
+ val commonPartsSize = bParts.zip(cParts).takeWhile { (basePart, childPart) -> basePart == childPart }.count()
+ bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
+ cParts.drop(commonPartsSize).joinTo(this, separator = "/")
+ }
+
+ return URI.create(buildString {
+ if (base.path != child.path) {
+ appendRelativePath()
+ }
+ child.rawQuery?.let {
+ append("?")
+ append(it)
+ }
+ child.rawFragment?.let {
+ append("#")
+ append(it)
+ }
+ })
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/javadoc/dokka-adapters.kt b/core/src/main/kotlin/javadoc/dokka-adapters.kt
index c98a3801..1329876a 100644
--- a/core/src/main/kotlin/javadoc/dokka-adapters.kt
+++ b/core/src/main/kotlin/javadoc/dokka-adapters.kt
@@ -1,21 +1,22 @@
package org.jetbrains.dokka.javadoc
+import com.google.inject.Binder
import com.google.inject.Inject
import com.sun.tools.doclets.formats.html.HtmlDoclet
import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.DefaultAnalysisComponent
+import org.jetbrains.dokka.Formats.DefaultAnalysisComponentServices
import org.jetbrains.dokka.Formats.FormatDescriptor
-import org.jetbrains.dokka.Kotlin.KotlinAsJavaDescriptorSignatureProvider
-import org.jetbrains.dokka.Model.DescriptorSignatureProvider
-import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
-import kotlin.reflect.KClass
+import org.jetbrains.dokka.Formats.KotlinAsJava
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.toType
-class JavadocGenerator @Inject constructor(val options: DocumentationOptions, val logger: DokkaLogger) : Generator {
+class JavadocGenerator @Inject constructor(val configuration: DokkaConfiguration, val logger: DokkaLogger) : Generator {
override fun buildPages(nodes: Iterable<DocumentationNode>) {
val module = nodes.single() as DocumentationModule
- DokkaConsoleLogger.report()
- HtmlDoclet.start(ModuleNodeAdapter(module, StandardReporter(logger), options.outputDir))
+ HtmlDoclet.start(ModuleNodeAdapter(module, StandardReporter(logger), configuration.outputDir))
}
override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
@@ -30,13 +31,12 @@ class JavadocGenerator @Inject constructor(val options: DocumentationOptions, va
}
}
-class JavadocFormatDescriptor : FormatDescriptor {
- override val formatServiceClass = null
- override val outlineServiceClass = null
- override val generatorServiceClass = JavadocGenerator::class
- override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
- override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
- override val sampleProcessingService = DefaultSampleProcessingService::class
- override val packageListServiceClass: KClass<out PackageListService>? = null
- override val descriptorSignatureProvider = KotlinAsJavaDescriptorSignatureProvider::class
+class JavadocFormatDescriptor :
+ FormatDescriptor,
+ DefaultAnalysisComponent,
+ DefaultAnalysisComponentServices by KotlinAsJava {
+
+ override fun configureOutput(binder: Binder): Unit = with(binder) {
+ bind<Generator>() toType JavadocGenerator::class
+ }
}
diff --git a/core/src/main/resources/dokka/format/java-layout-html.properties b/core/src/main/resources/dokka/format/java-layout-html.properties
new file mode 100644
index 00000000..fbb2bbed
--- /dev/null
+++ b/core/src/main/resources/dokka/format/java-layout-html.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatDescriptor
+description=Produces Kotlin Style Docs with Javadoc like layout \ No newline at end of file
diff --git a/core/src/main/resources/dokka/format/javadoc.properties b/core/src/main/resources/dokka/format/javadoc.properties
index a58317fc..a0d8a945 100644
--- a/core/src/main/resources/dokka/format/javadoc.properties
+++ b/core/src/main/resources/dokka/format/javadoc.properties
@@ -1 +1,2 @@
-class=org.jetbrains.dokka.javadoc.JavadocFormatDescriptor \ No newline at end of file
+class=org.jetbrains.dokka.javadoc.JavadocFormatDescriptor
+description=Produces Javadoc, with Kotlin declarations as Java view \ No newline at end of file
diff --git a/core/src/main/resources/dokka/generator/default.properties b/core/src/main/resources/dokka/generator/default.properties
deleted file mode 100644
index a4a16200..00000000
--- a/core/src/main/resources/dokka/generator/default.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-class=org.jetbrains.dokka.FileGenerator
-description=Default documentation generator \ No newline at end of file
diff --git a/core/src/main/resources/dokka/generator/javadoc.properties b/core/src/main/resources/dokka/generator/javadoc.properties
deleted file mode 100644
index 4075704f..00000000
--- a/core/src/main/resources/dokka/generator/javadoc.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-class=org.jetbrains.dokka.javadoc.JavadocGenerator
-description=Produces output via JDK javadoc tool \ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
new file mode 100644
index 00000000..c484a920
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Dokka
+description=Uses Dokka Default resolver \ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
new file mode 100644
index 00000000..3b61eabe
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.JavaLayoutHtmlInboundLinkResolutionService
+description=Resolver for JavaLayoutHtml \ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
new file mode 100644
index 00000000..0d5d7d17
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Javadoc
+description=Uses Javadoc Default resolver \ No newline at end of file
diff --git a/core/src/main/resources/dokka/language/java.properties b/core/src/main/resources/dokka/language/java.properties
deleted file mode 100644
index ab42f532..00000000
--- a/core/src/main/resources/dokka/language/java.properties
+++ /dev/null
@@ -1 +0,0 @@
-class=org.jetbrains.dokka.JavaLanguageService \ No newline at end of file
diff --git a/core/src/main/resources/dokka/language/kotlin.properties b/core/src/main/resources/dokka/language/kotlin.properties
deleted file mode 100644
index 16092007..00000000
--- a/core/src/main/resources/dokka/language/kotlin.properties
+++ /dev/null
@@ -1 +0,0 @@
-class=org.jetbrains.dokka.KotlinLanguageService \ No newline at end of file
diff --git a/core/src/main/resources/dokka/outline/yaml.properties b/core/src/main/resources/dokka/outline/yaml.properties
deleted file mode 100644
index 7268af37..00000000
--- a/core/src/main/resources/dokka/outline/yaml.properties
+++ /dev/null
@@ -1 +0,0 @@
-class=org.jetbrains.dokka.YamlOutlineService \ No newline at end of file