aboutsummaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:11 +0100
committerDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:49 +0100
commit39631054c58df5841ea268b7002b820ec55f6e0a (patch)
treecefedd8411c859243bd181568e16fcdd372a38c8 /core/src/main
parent797cb4732c53bf1e3b2091add8cf731fc436607f (diff)
downloaddokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.gz
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.bz2
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.zip
restructure Dokka build to use Gradle for everything except for the Maven plugin
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/kotlin/Analysis/AnalysisEnvironment.kt210
-rw-r--r--core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt550
-rw-r--r--core/src/main/kotlin/Formats/FormatDescriptor.kt12
-rw-r--r--core/src/main/kotlin/Formats/FormatService.kt20
-rw-r--r--core/src/main/kotlin/Formats/HtmlFormatService.kt169
-rw-r--r--core/src/main/kotlin/Formats/HtmlTemplateService.kt34
-rw-r--r--core/src/main/kotlin/Formats/JekyllFormatService.kt22
-rw-r--r--core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt121
-rw-r--r--core/src/main/kotlin/Formats/MarkdownFormatService.kt117
-rw-r--r--core/src/main/kotlin/Formats/OutlineService.kt29
-rw-r--r--core/src/main/kotlin/Formats/StandardFormats.kt38
-rw-r--r--core/src/main/kotlin/Formats/StructuredFormatService.kt367
-rw-r--r--core/src/main/kotlin/Formats/YamlOutlineService.kt24
-rw-r--r--core/src/main/kotlin/Generation/ConsoleGenerator.kt42
-rw-r--r--core/src/main/kotlin/Generation/FileGenerator.kt57
-rw-r--r--core/src/main/kotlin/Generation/Generator.kt19
-rw-r--r--core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt266
-rw-r--r--core/src/main/kotlin/Java/JavadocParser.kt170
-rw-r--r--core/src/main/kotlin/Kotlin/ContentBuilder.kt132
-rw-r--r--core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt43
-rw-r--r--core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt199
-rw-r--r--core/src/main/kotlin/Kotlin/DocumentationBuilder.kt653
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt64
-rw-r--r--core/src/main/kotlin/Kotlin/KotlinLanguageService.kt409
-rw-r--r--core/src/main/kotlin/Languages/JavaLanguageService.kt162
-rw-r--r--core/src/main/kotlin/Languages/LanguageService.kt41
-rw-r--r--core/src/main/kotlin/Locations/FoldersLocationService.kt29
-rw-r--r--core/src/main/kotlin/Locations/LocationService.kt78
-rw-r--r--core/src/main/kotlin/Locations/SingleFolderLocationService.kt19
-rw-r--r--core/src/main/kotlin/Markdown/MarkdownProcessor.kt50
-rw-r--r--core/src/main/kotlin/Model/Content.kt231
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt162
-rw-r--r--core/src/main/kotlin/Model/DocumentationReference.kt61
-rw-r--r--core/src/main/kotlin/Model/PackageDocs.kt60
-rw-r--r--core/src/main/kotlin/Model/SourceLinks.kt56
-rw-r--r--core/src/main/kotlin/Utilities/DokkaModule.kt73
-rw-r--r--core/src/main/kotlin/Utilities/Html.kt8
-rw-r--r--core/src/main/kotlin/Utilities/Path.kt5
-rw-r--r--core/src/main/kotlin/Utilities/ServiceLocator.kt78
-rw-r--r--core/src/main/kotlin/ant/dokka.kt108
-rw-r--r--core/src/main/kotlin/javadoc/docbase.kt501
-rw-r--r--core/src/main/kotlin/javadoc/dokka-adapters.kt30
-rw-r--r--core/src/main/kotlin/javadoc/reporter.kt34
-rw-r--r--core/src/main/kotlin/javadoc/source-position.kt18
-rw-r--r--core/src/main/kotlin/javadoc/tags.kt214
-rw-r--r--core/src/main/kotlin/main.kt262
-rw-r--r--core/src/main/resources/META-INF/MANIFEST.MF4
-rw-r--r--core/src/main/resources/dokka-antlib.xml3
-rw-r--r--core/src/main/resources/dokka/format/html-as-java.properties2
-rw-r--r--core/src/main/resources/dokka/format/html.properties2
-rw-r--r--core/src/main/resources/dokka/format/javadoc.properties1
-rw-r--r--core/src/main/resources/dokka/format/jekyll.properties2
-rw-r--r--core/src/main/resources/dokka/format/kotlin-website.properties2
-rw-r--r--core/src/main/resources/dokka/format/markdown.properties2
-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/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
-rw-r--r--core/src/main/resources/dokka/styles/style.css280
-rw-r--r--core/src/main/resources/format/javadoc.properties1
61 files changed, 6353 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt
new file mode 100644
index 00000000..a5e35a0e
--- /dev/null
+++ b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt
@@ -0,0 +1,210 @@
+package org.jetbrains.dokka
+
+import com.intellij.core.CoreApplicationEnvironment
+import com.intellij.core.CoreModuleManager
+import com.intellij.mock.MockComponentManager
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.extensions.Extensions
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.module.ModuleManager
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.OrderEnumerationHandler
+import com.intellij.openapi.roots.ProjectFileIndex
+import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.util.Disposer
+import com.intellij.psi.PsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.analyzer.AnalysisResult
+import org.jetbrains.kotlin.analyzer.ModuleContent
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.analyzer.ResolverForModule
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
+import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
+import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
+import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.config.ContentRoot
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.jetbrains.kotlin.container.getService
+import org.jetbrains.kotlin.context.ProjectContext
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.idea.caches.resolve.KotlinOutOfBlockCompletionModificationTracker
+import org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker
+import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.CompilerEnvironment
+import org.jetbrains.kotlin.resolve.jvm.JvmAnalyzerFacade
+import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import java.io.File
+
+/**
+ * Kotlin as a service entry point
+ *
+ * Configures environment, analyses files and provides facilities to perform code processing without emitting bytecode
+ *
+ * $messageCollector: required by compiler infrastructure and will receive all compiler messages
+ * $body: optional and can be used to configure environment without creating local variable
+ */
+public class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable {
+ val configuration = CompilerConfiguration();
+
+ init {
+ configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
+ }
+
+ fun createCoreEnvironment(): KotlinCoreEnvironment {
+ val environment = KotlinCoreEnvironment.createForProduction(this, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
+ val projectComponentManager = environment.project as MockComponentManager
+
+ val projectFileIndex = CoreProjectFileIndex(environment.project,
+ environment.configuration.getList(CommonConfigurationKeys.CONTENT_ROOTS))
+
+ val moduleManager = object : CoreModuleManager(environment.project, this) {
+ override fun getModules(): Array<out Module> = arrayOf(projectFileIndex.module)
+ }
+
+ CoreApplicationEnvironment.registerComponentInstance(projectComponentManager.picoContainer,
+ ModuleManager::class.java, moduleManager)
+
+ Extensions.registerAreaClass("IDEA_MODULE", null)
+ CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(),
+ OrderEnumerationHandler.EP_NAME, OrderEnumerationHandler.Factory::class.java)
+
+ projectComponentManager.registerService(ProjectFileIndex::class.java,
+ projectFileIndex)
+ projectComponentManager.registerService(ProjectRootManager::class.java,
+ CoreProjectRootManager(projectFileIndex))
+ projectComponentManager.registerService(LibraryModificationTracker::class.java,
+ LibraryModificationTracker(environment.project))
+ projectComponentManager.registerService(KotlinCacheService::class.java,
+ KotlinCacheService(environment.project))
+ projectComponentManager.registerService(KotlinOutOfBlockCompletionModificationTracker::class.java,
+ KotlinOutOfBlockCompletionModificationTracker())
+ return environment
+ }
+
+ fun createResolutionFacade(environment: KotlinCoreEnvironment): DokkaResolutionFacade {
+ val projectContext = ProjectContext(environment.project)
+ val sourceFiles = environment.getSourceFiles()
+
+ val module = object : ModuleInfo {
+ override val name: Name = Name.special("<module>")
+ override fun dependencies(): List<ModuleInfo> = listOf(this)
+ }
+ val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
+ "Dokka",
+ projectContext,
+ listOf(module),
+ { ModuleContent(sourceFiles, GlobalSearchScope.allScope(environment.project)) },
+ JvmPlatformParameters { module },
+ CompilerEnvironment
+ )
+
+ val resolverForModule = resolverForProject.resolverForModule(module)
+ return DokkaResolutionFacade(environment.project, resolverForProject.descriptorForModule(module), resolverForModule)
+ }
+
+ /**
+ * Classpath for this environment.
+ */
+ public val classpath: List<File>
+ get() = configuration.jvmClasspathRoots
+
+ /**
+ * Adds list of paths to classpath.
+ * $paths: collection of files to add
+ */
+ public fun addClasspath(paths: List<File>) {
+ configuration.addJvmClasspathRoots(paths)
+ }
+
+ /**
+ * Adds path to classpath.
+ * $path: path to add
+ */
+ public fun addClasspath(path: File) {
+ configuration.addJvmClasspathRoot(path)
+ }
+
+ /**
+ * List of source roots for this environment.
+ */
+ public val sources: List<String>
+ get() = configuration.get(CommonConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<KotlinSourceRoot>()
+ ?.map { it.path } ?: emptyList()
+
+ /**
+ * Adds list of paths to source roots.
+ * $list: collection of files to add
+ */
+ public fun addSources(list: List<String>) {
+ list.forEach {
+ configuration.add(CommonConfigurationKeys.CONTENT_ROOTS, contentRootFromPath(it))
+ }
+ }
+
+ public fun addRoots(list: List<ContentRoot>) {
+ configuration.addAll(CommonConfigurationKeys.CONTENT_ROOTS, list)
+ }
+
+ /**
+ * Disposes the environment and frees all associated resources.
+ */
+ public override fun dispose() {
+ Disposer.dispose(this)
+ }
+}
+
+public fun contentRootFromPath(path: String): ContentRoot {
+ val file = File(path)
+ return if (file.extension == "java") JavaSourceRoot(file, null) else KotlinSourceRoot(path)
+}
+
+
+class DokkaResolutionFacade(override val project: Project,
+ override val moduleDescriptor: ModuleDescriptor,
+ val resolverForModule: ResolverForModule) : ResolutionFacade {
+
+ val resolveSession: ResolveSession get() = getFrontendService(ResolveSession::class.java)
+
+ override fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode): BindingContext {
+ throw UnsupportedOperationException()
+ }
+
+ override fun analyzeFullyAndGetResult(elements: Collection<KtElement>): AnalysisResult {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any> getFrontendService(serviceClass: Class<T>): T {
+ return resolverForModule.componentProvider.getService(serviceClass)
+ }
+
+ override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any> getIdeService(serviceClass: Class<T>): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor {
+ return resolveSession.resolveToDescriptor(declaration)
+ }
+}
diff --git a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt
new file mode 100644
index 00000000..a1362fde
--- /dev/null
+++ b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt
@@ -0,0 +1,550 @@
+package org.jetbrains.dokka
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.components.BaseComponent
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.SdkAdditionalData
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.projectRoots.SdkTypeId
+import com.intellij.openapi.roots.*
+import com.intellij.openapi.roots.impl.ProjectOrderEnumerator
+import com.intellij.openapi.util.Condition
+import com.intellij.openapi.util.Key
+import com.intellij.openapi.util.UserDataHolderBase
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.util.messages.MessageBus
+import org.jetbrains.jps.model.module.JpsModuleSourceRootType
+import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
+import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot
+import org.jetbrains.kotlin.config.ContentRoot
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.picocontainer.PicoContainer
+import java.io.File
+
+/**
+ * Workaround for the lack of ability to create a ProjectFileIndex implementation using only
+ * classes from projectModel-{api,impl}.
+ */
+class CoreProjectFileIndex(val project: Project, contentRoots: List<ContentRoot>) : ProjectFileIndex, ModuleFileIndex {
+ val sourceRoots = contentRoots.filter { it !is JvmClasspathRoot }
+ val classpathRoots = contentRoots.filterIsInstance<JvmClasspathRoot>()
+
+ val module: Module = object : UserDataHolderBase(), Module {
+ override fun isDisposed(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getOptionValue(p0: String): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun clearOption(p0: String) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getName(): String = "<Dokka module>"
+
+ override fun getModuleWithLibrariesScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleWithDependentsScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleContentScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isLoaded(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun setOption(p0: String, p1: String) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleWithDependenciesScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleWithDependenciesAndLibrariesScope(p0: Boolean): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getProject(): Project = project
+
+ override fun getModuleContentWithDependenciesScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleFilePath(): String {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleTestsWithDependentsScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleScope(): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleScope(p0: Boolean): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleRuntimeScope(p0: Boolean): GlobalSearchScope {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleFile(): VirtualFile? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> getExtensions(p0: ExtensionPointName<T>): Array<out T> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getComponent(p0: String): BaseComponent? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> getComponent(p0: Class<T>, p1: T): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> getComponent(interfaceClass: Class<T>): T? {
+ if (interfaceClass == ModuleRootManager::class.java) {
+ return moduleRootManager as T
+ }
+ throw UnsupportedOperationException()
+ }
+
+ override fun getDisposed(): Condition<*> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> getComponents(p0: Class<T>): Array<out T> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getPicoContainer(): PicoContainer {
+ throw UnsupportedOperationException()
+ }
+
+ override fun hasComponent(p0: Class<*>): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getMessageBus(): MessageBus {
+ throw UnsupportedOperationException()
+ }
+
+ override fun dispose() {
+ throw UnsupportedOperationException()
+ }
+ }
+
+ private val sdk: Sdk = object : Sdk, RootProvider {
+ override fun getFiles(rootType: OrderRootType): Array<out VirtualFile> = classpathRoots
+ .map { StandardFileSystems.local().findFileByPath(it.file.path) }
+ .filterNotNull()
+ .toTypedArray()
+
+ override fun addRootSetChangedListener(p0: RootProvider.RootSetChangedListener) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun addRootSetChangedListener(p0: RootProvider.RootSetChangedListener, p1: Disposable) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getUrls(p0: OrderRootType): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun removeRootSetChangedListener(p0: RootProvider.RootSetChangedListener) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSdkModificator(): SdkModificator {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getName(): String = "<dokka SDK>"
+
+ override fun getRootProvider(): RootProvider = this
+
+ override fun getHomePath(): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getVersionString(): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSdkAdditionalData(): SdkAdditionalData? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun clone(): Any {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSdkType(): SdkTypeId {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getHomeDirectory(): VirtualFile? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> getUserData(p0: Key<T>): T? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> putUserData(p0: Key<T>, p1: T?) {
+ throw UnsupportedOperationException()
+ }
+ }
+
+ private val moduleSourceOrderEntry = object : ModuleSourceOrderEntry {
+ override fun getFiles(p0: OrderRootType?): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getPresentableName(): String {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getUrls(p0: OrderRootType?): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getOwnerModule(): Module = module
+
+ override fun <R : Any?> accept(p0: RootPolicy<R>?, p1: R?): R {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isValid(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun compareTo(other: OrderEntry?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getRootModel(): ModuleRootModel = moduleRootManager
+
+ override fun isSynthetic(): Boolean {
+ throw UnsupportedOperationException()
+ }
+ }
+
+ private val sdkOrderEntry = object : JdkOrderEntry {
+ override fun getJdkName(): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getJdk(): Sdk = sdk
+
+ override fun getFiles(p0: OrderRootType?): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getPresentableName(): String {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getUrls(p0: OrderRootType?): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getOwnerModule(): Module {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <R : Any?> accept(p0: RootPolicy<R>?, p1: R?): R {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isValid(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getRootFiles(p0: OrderRootType?): Array<out VirtualFile>? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getRootUrls(p0: OrderRootType?): Array<out String>? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun compareTo(other: OrderEntry?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isSynthetic(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ }
+
+ inner class MyModuleRootManager : ModuleRootManager() {
+ override fun getExcludeRoots(): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentEntries(): Array<out ContentEntry> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getExcludeRootUrls(): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <R : Any?> processOrder(p0: RootPolicy<R>?, p1: R): R {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRoots(p0: Boolean): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRoots(): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRoots(p0: JpsModuleSourceRootType<*>): MutableList<VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRoots(p0: MutableSet<out JpsModuleSourceRootType<*>>): MutableList<VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRoots(): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun orderEntries(): OrderEnumerator =
+ ProjectOrderEnumerator(project, null).using(object : RootModelProvider {
+ override fun getModules(): Array<out Module> = arrayOf(module)
+
+ override fun getRootModel(p0: Module): ModuleRootModel = this@MyModuleRootManager
+ })
+
+ override fun <T : Any?> getModuleExtension(p0: Class<T>?): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getDependencyModuleNames(): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModule(): Module = module
+
+ override fun isSdkInherited(): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getOrderEntries(): Array<out OrderEntry> = arrayOf(moduleSourceOrderEntry, sdkOrderEntry)
+
+ override fun getSourceRootUrls(): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRootUrls(p0: Boolean): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSdk(): Sdk? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRootUrls(): Array<out String> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleDependencies(): Array<out Module> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleDependencies(p0: Boolean): Array<out Module> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModifiableModel(): ModifiableRootModel {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isDependsOn(p0: Module?): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getFileIndex(): ModuleFileIndex {
+ return this@CoreProjectFileIndex
+ }
+
+ override fun getDependencies(): Array<out Module> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getDependencies(p0: Boolean): Array<out Module> {
+ throw UnsupportedOperationException()
+ }
+ }
+
+ val moduleRootManager = MyModuleRootManager()
+
+ override fun getContentRootForFile(p0: VirtualFile): VirtualFile? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRootForFile(p0: VirtualFile, p1: Boolean): VirtualFile? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getPackageNameByDirectory(p0: VirtualFile): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isInLibrarySource(file: VirtualFile): Boolean = false
+
+ override fun getClassRootForFile(file: VirtualFile): VirtualFile? =
+ classpathRoots.firstOrNull { it.contains(file) }?.let { StandardFileSystems.local().findFileByPath(it.file.path) }
+
+ override fun getOrderEntriesForFile(file: VirtualFile): List<OrderEntry> =
+ if (classpathRoots.contains(file)) listOf(sdkOrderEntry) else emptyList()
+
+ override fun isInLibraryClasses(file: VirtualFile): Boolean = classpathRoots.contains(file)
+
+ override fun isExcluded(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSourceRootForFile(p0: VirtualFile): VirtualFile? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isUnderIgnored(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isLibraryClassFile(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleForFile(file: VirtualFile): Module? =
+ if (sourceRoots.contains(file)) module else null
+
+ private fun List<ContentRoot>.contains(file: VirtualFile): Boolean = any { it.contains(file) }
+
+ override fun getModuleForFile(p0: VirtualFile, p1: Boolean): Module? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isInSource(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isIgnored(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isContentSourceFile(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isInSourceContent(file: VirtualFile): Boolean = sourceRoots.contains(file)
+
+ override fun iterateContent(p0: ContentIterator): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isInContent(p0: VirtualFile): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun iterateContentUnderDirectory(p0: VirtualFile, p1: ContentIterator): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isInTestSourceContent(file: VirtualFile): Boolean = false
+
+ override fun isUnderSourceRootOfType(p0: VirtualFile, p1: MutableSet<out JpsModuleSourceRootType<*>>): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getOrderEntryForFile(p0: VirtualFile): OrderEntry? {
+ throw UnsupportedOperationException()
+ }
+}
+
+class CoreProjectRootManager(val projectFileIndex: CoreProjectFileIndex) : ProjectRootManager() {
+ override fun orderEntries(): OrderEnumerator {
+ throw UnsupportedOperationException()
+ }
+
+ override fun orderEntries(p0: MutableCollection<out Module>): OrderEnumerator {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRootsFromAllModules(): Array<out VirtualFile>? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun setProjectSdk(p0: Sdk?) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun setProjectSdkName(p0: String?) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getModuleSourceRoots(p0: MutableSet<out JpsModuleSourceRootType<*>>): MutableList<VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentSourceRoots(): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getFileIndex(): ProjectFileIndex = projectFileIndex
+
+ override fun getProjectSdkName(): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getProjectSdk(): Sdk? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRoots(): Array<out VirtualFile> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getContentRootUrls(): MutableList<String> {
+ throw UnsupportedOperationException()
+ }
+
+}
+
+fun ContentRoot.contains(file: VirtualFile) = when (this) {
+ is JvmContentRoot -> {
+ val path = if (file.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL)
+ StandardFileSystems.getVirtualFileForJar(file)?.path ?: file.path
+ else
+ file.path
+ File(path).startsWith(this.file.absoluteFile)
+ }
+ is KotlinSourceRoot -> File(file.path).startsWith(File(this.path).absoluteFile)
+ else -> false
+}
diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt
new file mode 100644
index 00000000..0c7ca794
--- /dev/null
+++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.*
+import kotlin.reflect.KClass
+
+public 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>
+}
diff --git a/core/src/main/kotlin/Formats/FormatService.kt b/core/src/main/kotlin/Formats/FormatService.kt
new file mode 100644
index 00000000..7e66a6b7
--- /dev/null
+++ b/core/src/main/kotlin/Formats/FormatService.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka
+
+/**
+ * Abstract representation of a formatting service used to output documentation in desired format
+ *
+ * Bundled Formatters:
+ * * [HtmlFormatService] – outputs documentation to HTML format
+ * * [MarkdownFormatService] – outputs documentation in Markdown format
+ * * [TextFormatService] – outputs documentation in Text format
+ */
+public interface FormatService {
+ /** Returns extension for output files */
+ val extension: String
+
+ /** Appends formatted content to [StringBuilder](to) using specified [location] */
+ fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>)
+}
+
+/** Format content to [String] using specified [location] */
+fun FormatService.format(location: Location, nodes: Iterable<DocumentationNode>): String = StringBuilder().apply { appendNodes(location, this, nodes) }.toString()
diff --git a/core/src/main/kotlin/Formats/HtmlFormatService.kt b/core/src/main/kotlin/Formats/HtmlFormatService.kt
new file mode 100644
index 00000000..4d45e6cb
--- /dev/null
+++ b/core/src/main/kotlin/Formats/HtmlFormatService.kt
@@ -0,0 +1,169 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+
+public open class HtmlFormatService @Inject constructor(@Named("folders") locationService: LocationService,
+ signatureGenerator: LanguageService,
+ val templateService: HtmlTemplateService)
+: StructuredFormatService(locationService, signatureGenerator, "html"), OutlineFormatService {
+ override public fun formatText(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatSymbol(text: String): String {
+ return "<span class=\"symbol\">${formatText(text)}</span>"
+ }
+
+ override fun formatKeyword(text: String): String {
+ return "<span class=\"keyword\">${formatText(text)}</span>"
+ }
+
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return "<span class=\"identifier\">${formatText(text)}</span>"
+ }
+
+ override fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ to.append("<pre><code>")
+ to.append(line)
+ to.append("</code></pre>")
+ }
+
+ override fun appendHeader(to: StringBuilder, text: String, level: Int) {
+ to.appendln("<h$level>${text}</h$level>")
+ }
+
+ override fun appendParagraph(to: StringBuilder, text: String) {
+ to.appendln("<p>${text}</p>")
+ }
+
+ override fun appendLine(to: StringBuilder, text: String) {
+ to.appendln("${text}<br/>")
+ }
+
+ override fun appendLine(to: StringBuilder) {
+ to.appendln("<br/>")
+ }
+
+ override fun appendAnchor(to: StringBuilder, anchor: String) {
+ to.appendln("<a name=\"${anchor.htmlEscape()}\"></a>")
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<table>")
+ body()
+ to.appendln("</table>")
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<thead>")
+ body()
+ to.appendln("</thead>")
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tbody>")
+ body()
+ to.appendln("</tbody>")
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tr>")
+ body()
+ to.appendln("</tr>")
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<td>")
+ body()
+ to.appendln("</td>")
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "<a href=\"${href}\">${text}</a>"
+ }
+
+ override fun formatStrong(text: String): String {
+ return "<strong>${text}</strong>"
+ }
+
+ override fun formatEmphasis(text: String): String {
+ return "<emph>${text}</emph>"
+ }
+
+ override fun formatStrikethrough(text: String): String {
+ return "<s>${text}</s>"
+ }
+
+ override fun formatCode(code: String): String {
+ return "<code>${code}</code>"
+ }
+
+ override fun formatUnorderedList(text: String): String = "<ul>${text}</ul>"
+ override fun formatOrderedList(text: String): String = "<ol>${text}</ol>"
+
+ override fun formatListItem(text: String, kind: ListKind): String {
+ return "<li>${text}</li>"
+ }
+
+ override fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ return items.map { formatLink(it) }.joinToString("&nbsp;/&nbsp;")
+ }
+
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ templateService.appendHeader(to, getPageTitle(nodes), calcPathToRoot(location))
+ super.appendNodes(location, to, nodes)
+ templateService.appendFooter(to)
+ }
+
+ override fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ templateService.appendHeader(to, "Module Contents", calcPathToRoot(location))
+ super.appendOutline(location, to, nodes)
+ templateService.appendFooter(to)
+ }
+
+ private fun calcPathToRoot(location: Location): Path {
+ val path = Paths.get(location.path)
+ return path.parent?.relativize(Paths.get(locationService.root.path + '/')) ?: path
+ }
+
+ override fun getOutlineFileName(location: Location): File {
+ return File("${location.path}-outline.html")
+ }
+
+ override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) {
+ val link = ContentNodeDirectLink(node)
+ link.append(languageService.render(node, LanguageService.RenderMode.FULL))
+ val signature = formatText(location, link)
+ to.appendln("<a href=\"${location.path}\">${signature}</a><br/>")
+ }
+
+ override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<ul>")
+ body()
+ to.appendln("</ul>")
+ }
+
+ override fun formatNonBreakingSpace(): String = "&nbsp;"
+}
+
+fun getPageTitle(nodes: Iterable<DocumentationNode>): String? {
+ val breakdownByLocation = nodes.groupBy { node -> formatPageTitle(node) }
+ return breakdownByLocation.keys.singleOrNull()
+}
+
+fun formatPageTitle(node: DocumentationNode): String {
+ val path = node.path
+ if (path.size == 1) {
+ return path.first().name
+ }
+ val qualifiedName = node.qualifiedName()
+ if (qualifiedName.length == 0 && path.size == 2) {
+ return path.first().name + " / root package"
+ }
+ return path.first().name + " / " + qualifiedName
+}
diff --git a/core/src/main/kotlin/Formats/HtmlTemplateService.kt b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
new file mode 100644
index 00000000..ae42a31b
--- /dev/null
+++ b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.dokka
+
+import java.nio.file.Path
+
+public interface HtmlTemplateService {
+ fun appendHeader(to: StringBuilder, title: String?, basePath: Path)
+ fun appendFooter(to: StringBuilder)
+
+ companion object {
+ public fun default(css: String? = null): HtmlTemplateService {
+ return object : HtmlTemplateService {
+ override fun appendFooter(to: StringBuilder) {
+ to.appendln("</BODY>")
+ to.appendln("</HTML>")
+ }
+ override fun appendHeader(to: StringBuilder, title: String?, basePath: Path) {
+ to.appendln("<HTML>")
+ to.appendln("<HEAD>")
+ if (title != null) {
+ to.appendln("<title>$title</title>")
+ }
+ if (css != null) {
+ val cssPath = basePath.resolve(css)
+ to.appendln("<link rel=\"stylesheet\" href=\"$cssPath\">")
+ }
+ to.appendln("</HEAD>")
+ to.appendln("<BODY>")
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/core/src/main/kotlin/Formats/JekyllFormatService.kt b/core/src/main/kotlin/Formats/JekyllFormatService.kt
new file mode 100644
index 00000000..f81257d6
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JekyllFormatService.kt
@@ -0,0 +1,22 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+open class JekyllFormatService
+ @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService,
+ linkExtension: String = "md")
+: MarkdownFormatService(locationService, signatureGenerator, linkExtension) {
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ to.appendln("---")
+ appendFrontMatter(nodes, to)
+ to.appendln("---")
+ to.appendln("")
+ super.appendNodes(location, to, nodes)
+ }
+
+ protected open fun appendFrontMatter(nodes: Iterable<DocumentationNode>, to: StringBuilder) {
+ to.appendln("title: ${getPageTitle(nodes)}")
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
new file mode 100644
index 00000000..4eda7910
--- /dev/null
+++ b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
@@ -0,0 +1,121 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+public class KotlinWebsiteFormatService @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService)
+: JekyllFormatService(locationService, signatureGenerator, "html") {
+ private var needHardLineBreaks = false
+
+ override fun appendFrontMatter(nodes: Iterable<DocumentationNode>, to: StringBuilder) {
+ super.appendFrontMatter(nodes, to)
+ to.appendln("layout: api")
+ }
+
+ override public fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ items.drop(1)
+
+ if (items.count() > 1) {
+ return "<div class='api-docs-breadcrumbs'>" +
+ items.map { formatLink(it) }.joinToString(" / ") +
+ "</div>"
+ }
+
+ return ""
+ }
+
+ override public fun formatCode(code: String): String = if (code.length > 0) "<code>$code</code>" else ""
+
+ override fun formatStrikethrough(text: String): String = "<s>$text</s>"
+
+ override fun appendAsSignature(to: StringBuilder, node: ContentNode, block: () -> Unit) {
+ val contentLength = node.textLength
+ if (contentLength == 0) return
+ to.append("<div class=\"signature\">")
+ needHardLineBreaks = contentLength >= 62
+ try {
+ block()
+ } finally {
+ needHardLineBreaks = false
+ }
+ to.append("</div>")
+ }
+
+ override fun appendAsOverloadGroup(to: StringBuilder, block: () -> Unit) {
+ to.append("<div class=\"overload-group\">\n")
+ block()
+ to.append("</div>\n")
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "<a href=\"${href}\">${text}</a>"
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<table class=\"api-docs-table\">")
+ body()
+ to.appendln("</table>")
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<thead>")
+ body()
+ to.appendln("</thead>")
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tbody>")
+ body()
+ to.appendln("</tbody>")
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tr>")
+ body()
+ to.appendln("</tr>")
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<td markdown=\"1\">")
+ body()
+ to.appendln("\n</td>")
+ }
+
+ override public fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ if (language.isNotEmpty()) {
+ super.appendBlockCode(to, line, language)
+ } else {
+ to.append("<pre markdown=\"1\">")
+ to.append(line.trimStart())
+ to.append("</pre>")
+ }
+ }
+
+ override fun formatSymbol(text: String): String {
+ return "<span class=\"symbol\">${formatText(text)}</span>"
+ }
+
+ override fun formatKeyword(text: String): String {
+ return "<span class=\"keyword\">${formatText(text)}</span>"
+ }
+
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return "<span class=\"${identifierClassName(kind)}\">${formatText(text)}</span>"
+ }
+
+ override fun formatSoftLineBreak(): String = if (needHardLineBreaks)
+ "<br/>"
+ else
+ ""
+
+ override fun formatIndentedSoftLineBreak(): String = if (needHardLineBreaks)
+ "<br/>&nbsp;&nbsp;&nbsp;&nbsp;"
+ else
+ ""
+
+ private fun identifierClassName(kind: IdentifierKind) = when(kind) {
+ IdentifierKind.ParameterName -> "parameterName"
+ IdentifierKind.SummarizedTypeName -> "summarizedTypeName"
+ else -> "identifier"
+ }
+}
diff --git a/core/src/main/kotlin/Formats/MarkdownFormatService.kt b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
new file mode 100644
index 00000000..f694ae3e
--- /dev/null
+++ b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
@@ -0,0 +1,117 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+
+public open class MarkdownFormatService
+ @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService,
+ linkExtension: String = "md")
+: StructuredFormatService(locationService, signatureGenerator, "md", linkExtension) {
+ override public fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ return items.map { formatLink(it) }.joinToString(" / ")
+ }
+
+ override public fun formatText(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatSymbol(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatKeyword(text: String): String {
+ return text.htmlEscape()
+ }
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return text.htmlEscape()
+ }
+
+ override public fun formatCode(code: String): String {
+ return "`$code`"
+ }
+
+ override public fun formatUnorderedList(text: String): String = text + "\n"
+ override public fun formatOrderedList(text: String): String = text + "\n"
+
+ override fun formatListItem(text: String, kind: ListKind): String {
+ val itemText = if (text.endsWith("\n")) text else text + "\n"
+ return if (kind == ListKind.Unordered) "* $itemText" else "1. $itemText"
+ }
+
+ override public fun formatStrong(text: String): String {
+ return "**$text**"
+ }
+
+ override fun formatEmphasis(text: String): String {
+ return "*$text*"
+ }
+
+ override fun formatStrikethrough(text: String): String {
+ return "~~$text~~"
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "[$text]($href)"
+ }
+
+ override public fun appendLine(to: StringBuilder) {
+ to.appendln()
+ }
+
+ override public fun appendLine(to: StringBuilder, text: String) {
+ to.appendln(text)
+ }
+
+ override fun appendAnchor(to: StringBuilder, anchor: String) {
+ // no anchors in Markdown
+ }
+
+ override public fun appendParagraph(to: StringBuilder, text: String) {
+ to.appendln()
+ to.appendln(text)
+ to.appendln()
+ }
+
+ override public fun appendHeader(to: StringBuilder, text: String, level: Int) {
+ appendLine(to)
+ appendLine(to, "${"#".repeat(level)} $text")
+ appendLine(to)
+ }
+
+ override public fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ appendLine(to)
+ to.appendln("``` ${language}")
+ to.appendln(line)
+ to.appendln("```")
+ appendLine(to)
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln()
+ body()
+ to.appendln()
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ body()
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ body()
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.append("|")
+ body()
+ to.appendln()
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.append(" ")
+ body()
+ to.append(" |")
+ }
+
+ override fun formatNonBreakingSpace(): String = "&nbsp;"
+}
diff --git a/core/src/main/kotlin/Formats/OutlineService.kt b/core/src/main/kotlin/Formats/OutlineService.kt
new file mode 100644
index 00000000..6626cf51
--- /dev/null
+++ b/core/src/main/kotlin/Formats/OutlineService.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.dokka
+
+import java.io.File
+
+/**
+ * Service for building the outline of the package contents.
+ */
+public interface OutlineFormatService {
+ fun getOutlineFileName(location: Location): File
+
+ public fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder)
+ public fun appendOutlineLevel(to: StringBuilder, body: () -> Unit)
+
+ /** Appends formatted outline to [StringBuilder](to) using specified [location] */
+ public fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ for (node in nodes) {
+ appendOutlineHeader(location, node, to)
+ if (node.members.any()) {
+ val sortedMembers = node.members.sortedBy { it.name }
+ appendOutlineLevel(to) {
+ appendOutline(location, to, sortedMembers)
+ }
+ }
+ }
+ }
+
+ fun formatOutline(location: Location, nodes: Iterable<DocumentationNode>): String =
+ StringBuilder().apply { appendOutline(location, this, nodes) }.toString()
+}
diff --git a/core/src/main/kotlin/Formats/StandardFormats.kt b/core/src/main/kotlin/Formats/StandardFormats.kt
new file mode 100644
index 00000000..94e1b115
--- /dev/null
+++ b/core/src/main/kotlin/Formats/StandardFormats.kt
@@ -0,0 +1,38 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.*
+
+abstract class KotlinFormatDescriptorBase : FormatDescriptor {
+ override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
+
+ override val generatorServiceClass = FileGenerator::class
+}
+
+class HtmlFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = HtmlFormatService::class
+ override val outlineServiceClass = HtmlFormatService::class
+}
+
+class HtmlAsJavaFormatDescriptor : FormatDescriptor {
+ 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
+}
+
+class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = KotlinWebsiteFormatService::class
+ override val outlineServiceClass = YamlOutlineService::class
+}
+
+class JekyllFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = JekyllFormatService::class
+ override val outlineServiceClass = null
+}
+
+class MarkdownFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = MarkdownFormatService::class
+ override val outlineServiceClass = null
+}
diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt
new file mode 100644
index 00000000..32a2b68a
--- /dev/null
+++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt
@@ -0,0 +1,367 @@
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.LanguageService.RenderMode
+import java.util.*
+
+data class FormatLink(val text: String, val href: String)
+
+enum class ListKind {
+ Ordered,
+ Unordered
+}
+
+abstract class StructuredFormatService(locationService: LocationService,
+ val languageService: LanguageService,
+ override val extension: String,
+ val linkExtension: String = extension) : FormatService {
+ val locationService: LocationService = locationService.withExtension(linkExtension)
+
+ abstract fun appendBlockCode(to: StringBuilder, line: String, language: String)
+ abstract fun appendHeader(to: StringBuilder, text: String, level: Int = 1)
+ abstract fun appendParagraph(to: StringBuilder, text: String)
+ abstract fun appendLine(to: StringBuilder, text: String)
+ abstract fun appendLine(to: StringBuilder)
+ abstract fun appendAnchor(to: StringBuilder, anchor: String)
+
+ abstract fun appendTable(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableHeader(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableBody(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableRow(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableCell(to: StringBuilder, body: () -> Unit)
+
+ abstract fun formatText(text: String): String
+ abstract fun formatSymbol(text: String): String
+ abstract fun formatKeyword(text: String): String
+ abstract fun formatIdentifier(text: String, kind: IdentifierKind): String
+ fun formatEntity(text: String): String = text
+ abstract fun formatLink(text: String, href: String): String
+ open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.href)
+ abstract fun formatStrong(text: String): String
+ abstract fun formatStrikethrough(text: String): String
+ abstract fun formatEmphasis(text: String): String
+ abstract fun formatCode(code: String): String
+ abstract fun formatUnorderedList(text: String): String
+ abstract fun formatOrderedList(text: String): String
+ abstract fun formatListItem(text: String, kind: ListKind): String
+ abstract fun formatBreadcrumbs(items: Iterable<FormatLink>): String
+ abstract fun formatNonBreakingSpace(): String
+ open fun formatSoftLineBreak(): String = ""
+ open fun formatIndentedSoftLineBreak(): String = ""
+
+ open fun formatText(location: Location, nodes: Iterable<ContentNode>, listKind: ListKind = ListKind.Unordered): String {
+ return nodes.map { formatText(location, it, listKind) }.joinToString("")
+ }
+
+ open fun formatText(location: Location, content: ContentNode, listKind: ListKind = ListKind.Unordered): String {
+ return StringBuilder().apply {
+ when (content) {
+ is ContentText -> append(formatText(content.text))
+ is ContentSymbol -> append(formatSymbol(content.text))
+ is ContentKeyword -> append(formatKeyword(content.text))
+ is ContentIdentifier -> append(formatIdentifier(content.text, content.kind))
+ is ContentNonBreakingSpace -> append(formatNonBreakingSpace())
+ is ContentSoftLineBreak -> append(formatSoftLineBreak())
+ is ContentIndentedSoftLineBreak -> append(formatIndentedSoftLineBreak())
+ is ContentEntity -> append(formatEntity(content.text))
+ is ContentStrong -> append(formatStrong(formatText(location, content.children)))
+ is ContentStrikethrough -> append(formatStrikethrough(formatText(location, content.children)))
+ is ContentCode -> append(formatCode(formatText(location, content.children)))
+ is ContentEmphasis -> append(formatEmphasis(formatText(location, content.children)))
+ is ContentUnorderedList -> append(formatUnorderedList(formatText(location, content.children, ListKind.Unordered)))
+ is ContentOrderedList -> append(formatOrderedList(formatText(location, content.children, ListKind.Ordered)))
+ is ContentListItem -> append(formatListItem(formatText(location, content.children), listKind))
+
+ is ContentNodeLink -> {
+ val node = content.node
+ val linkTo = if (node != null) locationHref(location, node) else "#"
+ val linkText = formatText(location, content.children)
+ if (linkTo == ".") {
+ append(linkText)
+ } else {
+ append(formatLink(linkText, linkTo))
+ }
+ }
+ is ContentExternalLink -> {
+ val linkText = formatText(location, content.children)
+ if (content.href == ".") {
+ append(linkText)
+ } else {
+ append(formatLink(linkText, content.href))
+ }
+ }
+ is ContentParagraph -> appendParagraph(this, formatText(location, content.children))
+ is ContentBlockCode -> appendBlockCode(this, formatText(location, content.children), content.language)
+ is ContentHeading -> appendHeader(this, formatText(location, content.children), content.level)
+ is ContentBlock -> append(formatText(location, content.children))
+ }
+ }.toString()
+ }
+
+ open fun link(from: DocumentationNode, to: DocumentationNode): FormatLink = link(from, to, extension)
+
+ open fun link(from: DocumentationNode, to: DocumentationNode, extension: String): FormatLink {
+ return FormatLink(to.name, locationService.relativePathToLocation(from, to))
+ }
+
+ fun locationHref(from: Location, to: DocumentationNode): String {
+ val topLevelPage = to.references(DocumentationReference.Kind.TopLevelPage).singleOrNull()?.to
+ if (topLevelPage != null) {
+ return from.relativePathTo(locationService.location(topLevelPage), to.name)
+ }
+ return from.relativePathTo(locationService.location(to))
+ }
+
+ fun appendDocumentation(location: Location, to: StringBuilder, overloads: Iterable<DocumentationNode>) {
+ val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
+
+ for ((summary, items) in breakdownBySummary) {
+ appendAsOverloadGroup(to) {
+ items.forEach {
+ val rendered = languageService.render(it)
+ appendAsSignature(to, rendered) {
+ to.append(formatCode(formatText(location, rendered)))
+ it.appendSourceLink(to)
+ }
+ it.appendOverrides(to)
+ it.appendDeprecation(location, to)
+ }
+ // All items have exactly the same documentation, so we can use any item to render it
+ val item = items.first()
+ item.details(DocumentationNode.Kind.OverloadGroupNote).forEach {
+ to.append(formatText(location, it.content))
+ }
+ to.append(formatText(location, item.content.summary))
+ appendDescription(location, to, item)
+ appendLine(to)
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.isModuleOrPackage(): Boolean =
+ kind == DocumentationNode.Kind.Module || kind == DocumentationNode.Kind.Package
+
+ protected open fun appendAsSignature(to: StringBuilder, node: ContentNode, block: () -> Unit) {
+ block()
+ }
+
+ protected open fun appendAsOverloadGroup(to: StringBuilder, block: () -> Unit) {
+ block()
+ }
+
+ fun appendDescription(location: Location, to: StringBuilder, node: DocumentationNode) {
+ if (node.content.description != ContentEmpty) {
+ appendLine(to, formatText(location, node.content.description))
+ appendLine(to)
+ }
+ node.content.getSectionsWithSubjects().forEach {
+ appendSectionWithSubject(it.key, location, it.value, to)
+ }
+
+ for (section in node.content.sections.filter { it.subjectName == null }) {
+ appendLine(to, formatStrong(formatText(section.tag)))
+ appendLine(to, formatText(location, section))
+ }
+ }
+
+ fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> =
+ sections.filter { it.subjectName != null }.groupBy { it.tag }
+
+ fun appendSectionWithSubject(title: String, location: Location, subjectSections: List<ContentSection>, to: StringBuilder) {
+ appendHeader(to, title, 3)
+ subjectSections.forEach {
+ val subjectName = it.subjectName
+ if (subjectName != null) {
+ appendAnchor(to, subjectName)
+ to.append(formatCode(subjectName)).append(" - ")
+ to.append(formatText(location, it))
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.appendOverrides(to: StringBuilder) {
+ overrides.forEach {
+ to.append("Overrides ")
+ val location = locationService.relativePathToLocation(this, it)
+ appendLine(to, formatLink(FormatLink(it.owner!!.name + "." + it.name, location)))
+ }
+ }
+
+ private fun DocumentationNode.appendDeprecation(location: Location, to: StringBuilder) {
+ if (deprecation != null) {
+ val deprecationParameter = deprecation!!.details(DocumentationNode.Kind.Parameter).firstOrNull()
+ val deprecationValue = deprecationParameter?.details(DocumentationNode.Kind.Value)?.firstOrNull()
+ if (deprecationValue != null) {
+ to.append(formatStrong("Deprecated:")).append(" ")
+ appendLine(to, formatText(deprecationValue.name.removeSurrounding("\"")))
+ appendLine(to)
+ } else if (deprecation?.content != Content.Empty) {
+ to.append(formatStrong("Deprecated:")).append(" ")
+ to.append(formatText(location, deprecation!!.content))
+ } else {
+ appendLine(to, formatStrong("Deprecated"))
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.appendSourceLink(to: StringBuilder) {
+ val sourceUrl = details(DocumentationNode.Kind.SourceUrl).firstOrNull()
+ if (sourceUrl != null) {
+ to.append(" ")
+ appendLine(to, formatLink("(source)", sourceUrl.name))
+ } else {
+ appendLine(to)
+ }
+ }
+
+ fun appendLocation(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ val singleNode = nodes.singleOrNull()
+ if (singleNode != null && singleNode.isModuleOrPackage()) {
+ if (singleNode.kind == DocumentationNode.Kind.Package) {
+ appendHeader(to, "Package " + formatText(singleNode.name), 2)
+ }
+ to.append(formatText(location, singleNode.content))
+ } else {
+ val breakdownByName = nodes.groupBy { node -> node.name }
+ for ((name, items) in breakdownByName) {
+ appendHeader(to, formatText(name))
+ appendDocumentation(location, to, items)
+ }
+ }
+ }
+
+ private fun appendSection(location: Location, caption: String, nodes: List<DocumentationNode>, node: DocumentationNode, to: StringBuilder) {
+ if (nodes.any()) {
+ appendHeader(to, caption, 3)
+
+ val children = nodes.sortedBy { it.name }
+ val membersMap = children.groupBy { link(node, it) }
+
+ appendTable(to) {
+ appendTableBody(to) {
+ for ((memberLocation, members) in membersMap) {
+ appendTableRow(to) {
+ appendTableCell(to) {
+ to.append(formatLink(memberLocation))
+ }
+ appendTableCell(to) {
+ val breakdownBySummary = members.groupBy { formatText(location, it.summary) }
+ for ((summary, items) in breakdownBySummary) {
+ appendSummarySignatures(items, location, to)
+ if (!summary.isEmpty()) {
+ to.append(summary)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun appendSummarySignatures(items: List<DocumentationNode>, location: Location, to: StringBuilder) {
+ val summarySignature = languageService.summarizeSignatures(items)
+ if (summarySignature != null) {
+ appendAsSignature(to, summarySignature) {
+ appendLine(to, summarySignature.signatureToText(location))
+ }
+ return
+ }
+ val renderedSignatures = items.map { languageService.render(it, RenderMode.SUMMARY) }
+ renderedSignatures.subList(0, renderedSignatures.size - 1).forEach {
+ appendAsSignature(to, it) {
+ appendLine(to, it.signatureToText(location))
+ }
+ }
+ appendAsSignature(to, renderedSignatures.last()) {
+ to.append(renderedSignatures.last().signatureToText(location))
+ }
+ }
+
+ private fun ContentNode.signatureToText(location: Location): String {
+ return if (this is ContentBlock && this.isEmpty()) {
+ ""
+ } else {
+ val signatureAsCode = ContentCode()
+ signatureAsCode.append(this)
+ formatText(location, signatureAsCode)
+ }
+ }
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ val breakdownByLocation = nodes.groupBy { node ->
+ formatBreadcrumbs(node.path.filterNot { it.name.isEmpty() }.map { link(node, it) })
+ }
+
+ for ((breadcrumbs, items) in breakdownByLocation) {
+ appendLine(to, breadcrumbs)
+ appendLine(to)
+ appendLocation(location, to, items.filter { it.kind != DocumentationNode.Kind.ExternalClass })
+ }
+
+ for (node in nodes) {
+ if (node.kind == DocumentationNode.Kind.ExternalClass) {
+ appendSection(location, "Extensions for ${node.name}", node.members, node, to)
+ continue
+ }
+
+ appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to)
+ appendSection(location, "Types", node.members.filter { it.kind in DocumentationNode.Kind.classLike }, node, to)
+ appendSection(location, "Extensions for External Classes", node.members(DocumentationNode.Kind.ExternalClass), node, to)
+ appendSection(location, "Enum Values", node.members(DocumentationNode.Kind.EnumItem), node, to)
+ appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to)
+ appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to)
+ appendSection(location, "Inherited Properties", node.inheritedMembers(DocumentationNode.Kind.Property), node, to)
+ appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to)
+ appendSection(location, "Inherited Functions", node.inheritedMembers(DocumentationNode.Kind.Function), node, to)
+ appendSection(location, "Companion Object Properties", node.members(DocumentationNode.Kind.CompanionObjectProperty), node, to)
+ appendSection(location, "Companion Object Functions", node.members(DocumentationNode.Kind.CompanionObjectFunction), node, to)
+ appendSection(location, "Other members", node.members.filter {
+ it.kind !in setOf(
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.Interface,
+ DocumentationNode.Kind.Enum,
+ DocumentationNode.Kind.Object,
+ DocumentationNode.Kind.AnnotationClass,
+ DocumentationNode.Kind.Constructor,
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.Package,
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectProperty,
+ DocumentationNode.Kind.CompanionObjectFunction,
+ DocumentationNode.Kind.ExternalClass,
+ DocumentationNode.Kind.EnumItem
+ )
+ }, node, to)
+
+ val allExtensions = collectAllExtensions(node)
+ appendSection(location, "Extension Properties", allExtensions.filter { it.kind == DocumentationNode.Kind.Property }, node, to)
+ appendSection(location, "Extension Functions", allExtensions.filter { it.kind == DocumentationNode.Kind.Function }, node, to)
+ appendSection(location, "Companion Object Extension Properties", allExtensions.filter { it.kind == DocumentationNode.Kind.CompanionObjectProperty }, node, to)
+ appendSection(location, "Companion Object Extension Functions", allExtensions.filter { it.kind == DocumentationNode.Kind.CompanionObjectFunction }, node, to)
+ appendSection(location, "Inheritors",
+ node.inheritors.filter { it.kind != DocumentationNode.Kind.EnumItem }, node, to)
+ appendSection(location, "Links", node.links, node, to)
+
+ }
+ }
+
+ private fun collectAllExtensions(node: DocumentationNode): Collection<DocumentationNode> {
+ val result = LinkedHashSet<DocumentationNode>()
+ val visited = hashSetOf<DocumentationNode>()
+
+ fun collect(node: DocumentationNode) {
+ if (!visited.add(node)) return
+ result.addAll(node.extensions)
+ node.references(DocumentationReference.Kind.Superclass).forEach { collect(it.to) }
+ }
+
+ collect(node)
+
+ return result
+
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/YamlOutlineService.kt b/core/src/main/kotlin/Formats/YamlOutlineService.kt
new file mode 100644
index 00000000..7968824c
--- /dev/null
+++ b/core/src/main/kotlin/Formats/YamlOutlineService.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import java.io.File
+
+class YamlOutlineService @Inject constructor(val locationService: LocationService,
+ 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}")
+ }
+
+ override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
+ val indent = " ".repeat(outlineLevel)
+ to.appendln("$indent content:")
+ outlineLevel++
+ body()
+ outlineLevel--
+ }
+}
diff --git a/core/src/main/kotlin/Generation/ConsoleGenerator.kt b/core/src/main/kotlin/Generation/ConsoleGenerator.kt
new file mode 100644
index 00000000..803a16e4
--- /dev/null
+++ b/core/src/main/kotlin/Generation/ConsoleGenerator.kt
@@ -0,0 +1,42 @@
+package org.jetbrains.dokka
+
+public class ConsoleGenerator(val signatureGenerator: LanguageService, val locationService: LocationService) {
+ val IndentStep = " "
+
+ public fun generate(node: DocumentationNode, indent: String = "") {
+ println("@${locationService.location(node).path}")
+ generateHeader(node, indent)
+ //generateDetails(node, indent)
+ generateMembers(node, indent)
+ generateLinks(node, indent)
+ }
+
+ public 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()
+ }
+
+ public fun generateMembers(node: DocumentationNode, indent: String = "") {
+ val items = node.members.sortedBy { it.name }
+ for (child in items)
+ generate(child, indent + IndentStep)
+ }
+
+ public fun generateDetails(node: DocumentationNode, indent: String = "") {
+ val items = node.details
+ for (child in items)
+ generate(child, indent + " ")
+ }
+
+ public 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/FileGenerator.kt b/core/src/main/kotlin/Generation/FileGenerator.kt
new file mode 100644
index 00000000..a762bae3
--- /dev/null
+++ b/core/src/main/kotlin/Generation/FileGenerator.kt
@@ -0,0 +1,57 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.OutputStreamWriter
+
+public class FileGenerator @Inject constructor(val locationService: FileLocationService) : Generator {
+
+ @set:Inject(optional = true) var outlineService: OutlineFormatService? = null
+ @set:Inject(optional = true) lateinit var formatService: FormatService
+
+ 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
+ file.parentFile?.mkdirsOrFail()
+ try {
+ FileOutputStream(file).use {
+ OutputStreamWriter(it, Charsets.UTF_8).use {
+ it.write(formatService.format(location, items))
+ }
+ }
+ } catch (e: Throwable) {
+ println(e)
+ }
+ buildPages(items.flatMap { it.members })
+ }
+ }
+
+ override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
+ val outlineService = this.outlineService ?: return
+ for ((location, items) in nodes.groupBy { locationService.location(it) }) {
+ val file = outlineService.getOutlineFileName(location)
+ file.parentFile?.mkdirsOrFail()
+ FileOutputStream(file).use {
+ OutputStreamWriter(it, Charsets.UTF_8).use {
+ it.write(outlineService.formatOutline(location, items))
+ }
+ }
+ }
+ }
+
+ override fun buildSupportFiles() {
+ FileOutputStream(locationService.location(listOf("style.css"), false).file).use {
+ javaClass.getResourceAsStream("/dokka/styles/style.css").copyTo(it)
+ }
+ }
+}
+
+private fun File.mkdirsOrFail() {
+ if (!mkdirs() && !exists()) {
+ throw IOException("Failed to create directory $this")
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Generation/Generator.kt b/core/src/main/kotlin/Generation/Generator.kt
new file mode 100644
index 00000000..ac10a6a5
--- /dev/null
+++ b/core/src/main/kotlin/Generation/Generator.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.dokka
+
+public interface Generator {
+ fun buildPages(nodes: Iterable<DocumentationNode>)
+ fun buildOutlines(nodes: Iterable<DocumentationNode>)
+ fun buildSupportFiles()
+}
+
+fun Generator.buildAll(nodes: Iterable<DocumentationNode>) {
+ buildPages(nodes)
+ buildOutlines(nodes)
+ buildSupportFiles()
+}
+
+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))
diff --git a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
new file mode 100644
index 00000000..3c9875cd
--- /dev/null
+++ b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt
@@ -0,0 +1,266 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.psi.*
+import org.jetbrains.dokka.DocumentationNode.Kind
+
+fun getSignature(element: PsiElement?) = when(element) {
+ is PsiClass -> element.qualifiedName
+ is PsiField -> element.containingClass!!.qualifiedName + "#" + element.name
+ is PsiMethod ->
+ element.containingClass!!.qualifiedName + "#" + element.name + "(" +
+ element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")"
+ else -> null
+}
+
+private fun PsiType.typeSignature(): String = when(this) {
+ is PsiArrayType -> "Array<${componentType.typeSignature()}>"
+ else -> mapTypeName(this)
+}
+
+private fun mapTypeName(psiType: PsiType): String = when (psiType) {
+ is PsiPrimitiveType -> psiType.canonicalText
+ is PsiClassType -> psiType.resolve()?.qualifiedName ?: psiType.className
+ is PsiEllipsisType -> mapTypeName(psiType.componentType)
+ is PsiArrayType -> "Array"
+ else -> psiType.canonicalText
+}
+
+interface JavaDocumentationBuilder {
+ fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>)
+}
+
+class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
+ private val options: DocumentationOptions
+ private val refGraph: NodeReferenceGraph
+ private val docParser: JavaDocumentationParser
+
+ @Inject constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph) {
+ this.options = options
+ this.refGraph = refGraph
+ this.docParser = JavadocParser(refGraph)
+ }
+
+ constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) {
+ this.options = options
+ this.refGraph = refGraph
+ this.docParser = docParser
+ }
+
+ override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
+ if (file.classes.all { skipElement(it) }) {
+ return
+ }
+ val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap())
+ appendClasses(packageNode, file.classes)
+ }
+
+ fun appendClasses(packageNode: DocumentationNode, classes: Array<PsiClass>) {
+ packageNode.appendChildren(classes) { build() }
+ }
+
+ fun register(element: PsiElement, node: DocumentationNode) {
+ val signature = getSignature(element)
+ if (signature != null) {
+ refGraph.register(signature, node)
+ }
+ }
+
+ fun link(node: DocumentationNode, element: PsiElement?) {
+ val qualifiedName = getSignature(element)
+ if (qualifiedName != null) {
+ refGraph.link(node, qualifiedName, DocumentationReference.Kind.Link)
+ }
+ }
+
+ fun link(element: PsiElement?, node: DocumentationNode, kind: DocumentationReference.Kind) {
+ val qualifiedName = getSignature(element)
+ if (qualifiedName != null) {
+ refGraph.link(qualifiedName, node, kind)
+ }
+ }
+
+ fun nodeForElement(element: PsiNamedElement,
+ kind: Kind,
+ name: String = element.name ?: "<anonymous>"): DocumentationNode {
+ val (docComment, deprecatedContent) = docParser.parseDocumentation(element)
+ val node = DocumentationNode(name, docComment, kind)
+ if (element is PsiModifierListOwner) {
+ node.appendModifiers(element)
+ val modifierList = element.modifierList
+ if (modifierList != null) {
+ modifierList.annotations.filter { !ignoreAnnotation(it) }.forEach {
+ val annotation = it.build()
+ node.append(annotation,
+ if (it.qualifiedName == "java.lang.Deprecated") DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation)
+ }
+ }
+ }
+ if (deprecatedContent != null) {
+ val deprecationNode = DocumentationNode("", deprecatedContent, Kind.Modifier)
+ node.append(deprecationNode, DocumentationReference.Kind.Deprecation)
+ }
+ if (element is PsiDocCommentOwner && element.isDeprecated && node.deprecation == null) {
+ val deprecationNode = DocumentationNode("", Content.of(ContentText("Deprecated")), Kind.Modifier)
+ node.append(deprecationNode, DocumentationReference.Kind.Deprecation)
+ }
+ return node
+ }
+
+ fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) {
+ "java.lang.SuppressWarnings" -> true
+ else -> false
+ }
+
+ fun <T : Any> DocumentationNode.appendChildren(elements: Array<T>,
+ kind: DocumentationReference.Kind = DocumentationReference.Kind.Member,
+ buildFn: T.() -> DocumentationNode) {
+ elements.forEach {
+ if (!skipElement(it)) {
+ append(it.buildFn(), kind)
+ }
+ }
+ }
+
+ private fun skipElement(element: Any) = skipElementByVisibility(element) || hasSuppressTag(element)
+
+ private fun skipElementByVisibility(element: Any): Boolean =
+ !options.includeNonPublic && element is PsiModifierListOwner &&
+ (element.hasModifierProperty(PsiModifier.PRIVATE) || element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL))
+
+ private fun hasSuppressTag(element: Any) =
+ element is PsiDocCommentOwner && element.docComment?.let { it.findTagByName("suppress") != null } ?: false
+
+ fun <T : Any> DocumentationNode.appendMembers(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
+ appendChildren(elements, DocumentationReference.Kind.Member, buildFn)
+
+ fun <T : Any> DocumentationNode.appendDetails(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
+ appendChildren(elements, DocumentationReference.Kind.Detail, buildFn)
+
+ fun PsiClass.build(): DocumentationNode {
+ val kind = when {
+ isInterface -> DocumentationNode.Kind.Interface
+ isEnum -> DocumentationNode.Kind.Enum
+ isAnnotationType -> DocumentationNode.Kind.AnnotationClass
+ else -> DocumentationNode.Kind.Class
+ }
+ val node = nodeForElement(this, kind)
+ superTypes.filter { !ignoreSupertype(it) }.forEach {
+ node.appendType(it, Kind.Supertype)
+ val superClass = it.resolve()
+ if (superClass != null) {
+ link(superClass, node, DocumentationReference.Kind.Inheritor)
+ }
+ }
+ node.appendDetails(typeParameters) { build() }
+ node.appendMembers(methods) { build() }
+ node.appendMembers(fields) { build() }
+ node.appendMembers(innerClasses) { build() }
+ register(this, node)
+ return node
+ }
+
+ fun ignoreSupertype(psiType: PsiClassType): Boolean =
+ psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object")
+
+ fun PsiClassType.isClass(qName: String): Boolean {
+ val shortName = qName.substringAfterLast('.')
+ if (className == shortName) {
+ val psiClass = resolve()
+ return psiClass?.qualifiedName == qName
+ }
+ return false
+ }
+
+ fun PsiField.build(): DocumentationNode {
+ val node = nodeForElement(this, nodeKind())
+ node.appendType(type)
+ node.appendModifiers(this)
+ register(this, node)
+ return node
+ }
+
+ private fun PsiField.nodeKind(): Kind = when {
+ this is PsiEnumConstant -> Kind.EnumItem
+ else -> Kind.Field
+ }
+
+ fun PsiMethod.build(): DocumentationNode {
+ val node = nodeForElement(this, nodeKind(),
+ if (isConstructor) "<init>" else name)
+
+ if (!isConstructor) {
+ node.appendType(returnType)
+ }
+ node.appendDetails(parameterList.parameters) { build() }
+ node.appendDetails(typeParameters) { build() }
+ register(this, node)
+ return node
+ }
+
+ private fun PsiMethod.nodeKind(): Kind = when {
+ isConstructor -> Kind.Constructor
+ else -> Kind.Function
+ }
+
+ fun PsiParameter.build(): DocumentationNode {
+ val node = nodeForElement(this, Kind.Parameter)
+ node.appendType(type)
+ if (type is PsiEllipsisType) {
+ node.appendTextNode("vararg", Kind.Modifier, DocumentationReference.Kind.Detail)
+ }
+ return node
+ }
+
+ fun PsiTypeParameter.build(): DocumentationNode {
+ val node = nodeForElement(this, Kind.TypeParameter)
+ extendsListTypes.forEach { node.appendType(it, Kind.UpperBound) }
+ implementsListTypes.forEach { node.appendType(it, Kind.UpperBound) }
+ return node
+ }
+
+ fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) {
+ val modifierList = element.modifierList ?: return
+
+ PsiModifier.MODIFIERS.forEach {
+ if (modifierList.hasExplicitModifier(it)) {
+ appendTextNode(it, Kind.Modifier)
+ }
+ }
+ }
+
+ fun DocumentationNode.appendType(psiType: PsiType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) {
+ if (psiType == null) {
+ return
+ }
+ append(psiType.build(kind), DocumentationReference.Kind.Detail)
+ }
+
+ fun PsiType.build(kind: DocumentationNode.Kind = DocumentationNode.Kind.Type): DocumentationNode {
+ val name = mapTypeName(this)
+ val node = DocumentationNode(name, Content.Empty, kind)
+ if (this is PsiClassType) {
+ node.appendDetails(parameters) { build(Kind.Type) }
+ link(node, resolve())
+ }
+ if (this is PsiArrayType && this !is PsiEllipsisType) {
+ node.append(componentType.build(Kind.Type), DocumentationReference.Kind.Detail)
+ }
+ return node
+ }
+
+ fun PsiAnnotation.build(): DocumentationNode {
+ val node = DocumentationNode(nameReferenceElement?.text ?: "<?>", Content.Empty, DocumentationNode.Kind.Annotation)
+ parameterList.attributes.forEach {
+ val parameter = DocumentationNode(it.name ?: "value", Content.Empty, DocumentationNode.Kind.Parameter)
+ val value = it.value
+ if (value != null) {
+ val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text
+ val valueNode = DocumentationNode(valueText, Content.Empty, DocumentationNode.Kind.Value)
+ parameter.append(valueNode, DocumentationReference.Kind.Detail)
+ }
+ node.append(parameter, DocumentationReference.Kind.Detail)
+ }
+ return node
+ }
+}
diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt
new file mode 100644
index 00000000..1378a5a7
--- /dev/null
+++ b/core/src/main/kotlin/Java/JavadocParser.kt
@@ -0,0 +1,170 @@
+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 org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
+import org.jsoup.nodes.TextNode
+
+data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) {
+ companion object {
+ val Empty = JavadocParseResult(Content.Empty, null)
+ }
+}
+
+interface JavaDocumentationParser {
+ fun parseDocumentation(element: PsiNamedElement): JavadocParseResult
+}
+
+class JavadocParser(private val refGraph: NodeReferenceGraph) : JavaDocumentationParser {
+ 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() })
+ docComment.tags.forEach { tag ->
+ when(tag.name) {
+ "see" -> result.convertSeeTag(tag)
+ "deprecated" -> {
+ deprecatedContent = Content()
+ deprecatedContent!!.convertJavadocElements(tag.contentElements())
+ }
+ else -> {
+ val subjectName = tag.getSubjectName()
+ val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName)
+
+ section.convertJavadocElements(tag.contentElements())
+ }
+ }
+ }
+ return JavadocParseResult(result, deprecatedContent)
+ }
+
+ 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 }
+ return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements
+ }
+
+ private fun ContentBlock.convertJavadocElements(elements: Iterable<PsiElement>) {
+ val htmlBuilder = StringBuilder()
+ elements.forEach {
+ if (it is PsiInlineDocTag) {
+ htmlBuilder.append(convertInlineDocTag(it))
+ } else {
+ htmlBuilder.append(it.text)
+ }
+ }
+ val doc = Jsoup.parse(htmlBuilder.toString().trimStart())
+ doc.body().childNodes().forEach {
+ convertHtmlNode(it)
+ }
+ }
+
+ private fun ContentBlock.convertHtmlNode(node: Node) {
+ if (node is TextNode) {
+ append(ContentText(node.text()))
+ } else if (node is Element) {
+ val childBlock = createBlock(node)
+ node.childNodes().forEach {
+ childBlock.convertHtmlNode(it)
+ }
+ append(childBlock)
+ }
+ }
+
+ private fun createBlock(element: Element): ContentBlock = when(element.tagName()) {
+ "p" -> ContentParagraph()
+ "b", "strong" -> ContentStrong()
+ "i", "em" -> ContentEmphasis()
+ "s", "del" -> ContentStrikethrough()
+ "code" -> ContentCode()
+ "pre" -> ContentBlockCode()
+ "ul" -> ContentUnorderedList()
+ "ol" -> ContentOrderedList()
+ "li" -> ContentListItem()
+ "a" -> createLink(element)
+ else -> ContentBlock()
+ }
+
+ private fun createLink(element: Element): ContentBlock {
+ val docref = element.attr("docref")
+ if (docref != null) {
+ return ContentNodeLazyLink(docref, { -> refGraph.lookup(docref)})
+ }
+ val href = element.attr("href")
+ if (href != null) {
+ return ContentExternalLink(href)
+ } else {
+ return ContentBlock()
+ }
+ }
+
+ private fun MutableContent.convertSeeTag(tag: PsiDocTag) {
+ val linkElement = tag.linkElement()
+ if (linkElement == null) {
+ return
+ }
+ val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null)
+ val linkSignature = resolveLink(linkElement)
+ val text = ContentText(linkElement.text)
+ if (linkSignature != null) {
+ val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookup(linkSignature)})
+ linkNode.append(text)
+ seeSection.append(linkNode)
+ } else {
+ seeSection.append(text)
+ }
+ }
+
+ private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) {
+ "link", "linkplain" -> {
+ val valueElement = tag.linkElement()
+ val linkSignature = resolveLink(valueElement)
+ if (linkSignature != null) {
+ val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text
+ val link = "<a docref=\"$linkSignature\">${labelText.htmlEscape()}</a>"
+ if (tag.name == "link") "<code>$link</code>" else link
+ }
+ else if (valueElement != null) {
+ valueElement.text
+ } else {
+ ""
+ }
+ }
+ "code", "literal" -> {
+ val text = StringBuilder()
+ tag.dataElements.forEach { text.append(it.text) }
+ val escaped = text.toString().trimStart().htmlEscape()
+ if (tag.name == "code") "<code>$escaped</code>" else escaped
+ }
+ else -> tag.text
+ }
+
+ private fun PsiDocTag.linkElement(): PsiElement? =
+ valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+
+ private fun resolveLink(valueElement: PsiElement?): String? {
+ val target = valueElement?.reference?.resolve()
+ if (target != null) {
+ return getSignature(target)
+ }
+ return null
+ }
+
+ fun PsiDocTag.getSubjectName(): String? {
+ if (name == "param" || name == "throws" || name == "exception") {
+ return valueElement?.text
+ }
+ return null
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/ContentBuilder.kt b/core/src/main/kotlin/Kotlin/ContentBuilder.kt
new file mode 100644
index 00000000..c4bb18de
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/ContentBuilder.kt
@@ -0,0 +1,132 @@
+package org.jetbrains.dokka
+
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.intellij.markdown.html.entities.EntityConverter
+import java.util.*
+
+public fun buildContent(tree: MarkdownNode, linkResolver: (String) -> ContentBlock, inline: Boolean = false): MutableContent {
+ val result = MutableContent()
+ if (inline) {
+ buildInlineContentTo(tree, result, linkResolver)
+ }
+ else {
+ buildContentTo(tree, result, linkResolver)
+ }
+ return result
+}
+
+public fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: (String) -> ContentBlock) {
+// println(tree.toTestString())
+ val nodeStack = ArrayDeque<ContentBlock>()
+ nodeStack.push(target)
+
+ tree.visit {node, processChildren ->
+ val parent = nodeStack.peek()
+
+ fun appendNodeWithChildren(content: ContentBlock) {
+ nodeStack.push(content)
+ processChildren()
+ parent.append(nodeStack.pop())
+ }
+
+ when (node.type) {
+ MarkdownElementTypes.ATX_1 -> appendNodeWithChildren(ContentHeading(1))
+ MarkdownElementTypes.ATX_2 -> appendNodeWithChildren(ContentHeading(2))
+ MarkdownElementTypes.ATX_3 -> appendNodeWithChildren(ContentHeading(3))
+ MarkdownElementTypes.ATX_4 -> appendNodeWithChildren(ContentHeading(4))
+ MarkdownElementTypes.ATX_5 -> appendNodeWithChildren(ContentHeading(5))
+ MarkdownElementTypes.ATX_6 -> appendNodeWithChildren(ContentHeading(6))
+ MarkdownElementTypes.UNORDERED_LIST -> appendNodeWithChildren(ContentUnorderedList())
+ MarkdownElementTypes.ORDERED_LIST -> appendNodeWithChildren(ContentOrderedList())
+ MarkdownElementTypes.LIST_ITEM -> appendNodeWithChildren(ContentListItem())
+ MarkdownElementTypes.EMPH -> appendNodeWithChildren(ContentEmphasis())
+ MarkdownElementTypes.STRONG -> appendNodeWithChildren(ContentStrong())
+ MarkdownElementTypes.CODE_SPAN -> appendNodeWithChildren(ContentCode())
+ MarkdownElementTypes.CODE_BLOCK,
+ MarkdownElementTypes.CODE_FENCE -> appendNodeWithChildren(ContentBlockCode())
+ MarkdownElementTypes.PARAGRAPH -> appendNodeWithChildren(ContentParagraph())
+
+ MarkdownElementTypes.INLINE_LINK -> {
+ val label = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)
+ val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
+ if (label != null) {
+ if (destination != null) {
+ val link = ContentExternalLink(destination.text)
+ link.append(ContentText(label.text))
+ parent.append(link)
+ } else {
+ val link = ContentExternalLink(label.text)
+ link.append(ContentText(label.text))
+ parent.append(link)
+ }
+ }
+ }
+ MarkdownElementTypes.SHORT_REFERENCE_LINK,
+ MarkdownElementTypes.FULL_REFERENCE_LINK -> {
+ val label = node.child(MarkdownElementTypes.LINK_LABEL)?.child(MarkdownTokenTypes.TEXT)
+ if (label != null) {
+ val link = linkResolver(label.text)
+ val linkText = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)
+ link.append(ContentText(linkText?.text ?: label.text))
+ parent.append(link)
+ }
+ }
+ MarkdownTokenTypes.WHITE_SPACE,
+ MarkdownTokenTypes.EOL -> {
+ if (keepWhitespace(nodeStack.peek()) && node.parent?.children?.last() != node) {
+ parent.append(ContentText(node.text))
+ }
+ }
+
+ MarkdownTokenTypes.CODE -> {
+ val block = ContentBlockCode()
+ block.append(ContentText(node.text))
+ parent.append(block)
+ }
+
+ MarkdownTokenTypes.TEXT -> {
+ fun createEntityOrText(text: String): ContentNode {
+ if (text == "&amp;" || text == "&quot;" || text == "&lt;" || text == "&gt;") {
+ return ContentEntity(text)
+ }
+ if (text == "&") {
+ return ContentEntity("&amp;")
+ }
+ val decodedText = EntityConverter.replaceEntities(text, true, true)
+ if (decodedText != text) {
+ return ContentEntity(text)
+ }
+ return ContentText(text)
+ }
+
+ parent.append(createEntityOrText(node.text))
+ }
+
+ MarkdownTokenTypes.COLON,
+ MarkdownTokenTypes.DOUBLE_QUOTE,
+ MarkdownTokenTypes.LT,
+ MarkdownTokenTypes.GT,
+ MarkdownTokenTypes.LPAREN,
+ MarkdownTokenTypes.RPAREN,
+ MarkdownTokenTypes.LBRACKET,
+ MarkdownTokenTypes.RBRACKET,
+ MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
+ parent.append(ContentText(node.text))
+ }
+ else -> {
+ processChildren()
+ }
+ }
+ }
+}
+
+private fun keepWhitespace(node: ContentNode) = node is ContentParagraph || node is ContentSection
+
+public fun buildInlineContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: (String) -> ContentBlock) {
+ val inlineContent = tree.children.singleOrNull { it.type == MarkdownElementTypes.PARAGRAPH }?.children ?: listOf(tree)
+ inlineContent.forEach {
+ buildContentTo(it, target, linkResolver)
+ }
+}
+
diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
new file mode 100644
index 00000000..2569bc71
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
+
+class DeclarationLinkResolver
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val refGraph: NodeReferenceGraph,
+ val logger: DokkaLogger) {
+ fun resolveContentLink(fromDescriptor: DeclarationDescriptor, href: String): ContentBlock {
+ val symbol = try {
+ val symbols = resolveKDocLink(resolutionFacade, fromDescriptor, null, href.split('.').toList())
+ findTargetSymbol(symbols)
+ } catch(e: Exception) {
+ null
+ }
+
+ // don't include unresolved links in generated doc
+ // assume that if an href doesn't contain '/', it's not an attempt to reference an external file
+ if (symbol != null) {
+ return ContentNodeLazyLink(href, { -> refGraph.lookup(symbol.signature()) })
+ }
+ if ("/" in href) {
+ return ContentExternalLink(href)
+ }
+ logger.warn("Unresolved link to $href in doc comment of ${fromDescriptor.signatureWithSourceLocation()}")
+ return ContentExternalLink("#")
+ }
+
+ fun findTargetSymbol(symbols: Collection<DeclarationDescriptor>): DeclarationDescriptor? {
+ if (symbols.isEmpty()) {
+ return null
+ }
+ val symbol = symbols.first()
+ if (symbol is CallableMemberDescriptor && symbol.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
+ return symbol.overriddenDescriptors.firstOrNull()
+ }
+ return symbol
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
new file mode 100644
index 00000000..b7705ec9
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt
@@ -0,0 +1,199 @@
+package org.jetbrains.dokka.Kotlin
+
+import com.google.inject.Inject
+import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.*
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.idea.kdoc.KDocFinder
+import org.jetbrains.kotlin.idea.kdoc.getResolutionScope
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
+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.name.Name
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
+import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+
+class DescriptorDocumentationParser
+ @Inject constructor(val options: DocumentationOptions,
+ val logger: DokkaLogger,
+ val linkResolver: DeclarationLinkResolver,
+ val resolutionFacade: DokkaResolutionFacade,
+ val refGraph: NodeReferenceGraph)
+{
+ fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content =
+ parseDocumentationAndDetails(descriptor, inline).first
+
+ fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor, inline: Boolean = false): Pair<Content, (DocumentationNode) -> Unit> {
+ if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) {
+ return parseJavadoc(descriptor)
+ }
+
+ val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor)
+ if (kdoc == null) {
+ if (options.reportUndocumented && !descriptor.isDeprecated() &&
+ descriptor !is ValueParameterDescriptor && descriptor !is TypeParameterDescriptor &&
+ descriptor !is PropertyAccessorDescriptor) {
+ logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}")
+ }
+ return Content.Empty to { node -> }
+ }
+ var kdocText = kdoc.getContent()
+ // workaround for code fence parsing problem in IJ markdown parser
+ if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) {
+ kdocText += "\n"
+ }
+ val tree = parseMarkdown(kdocText)
+ val content = buildContent(tree, { href -> linkResolver.resolveContentLink(descriptor, href) }, inline)
+ if (kdoc is KDocSection) {
+ val tags = kdoc.getTags()
+ tags.forEach {
+ when (it.name) {
+ "sample" ->
+ content.append(functionBody(descriptor, it.getSubjectName()))
+ "see" ->
+ content.addTagToSeeAlso(descriptor, it)
+ else -> {
+ val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
+ val sectionContent = it.getContent()
+ val markdownNode = parseMarkdown(sectionContent)
+ buildInlineContentTo(markdownNode, section, { href -> linkResolver.resolveContentLink(descriptor, href) })
+ }
+ }
+ }
+ }
+ return content to { node -> }
+ }
+
+ /**
+ * Special case for generating stdlib documentation (the Any class to which the override chain will resolve
+ * is not the same one as the Any class included in the source scope).
+ */
+ fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? {
+ if (descriptor !is CallableMemberDescriptor) {
+ return null
+ }
+ val name = descriptor.name.asString()
+ if (name == "equals" || name == "hashCode" || name == "toString") {
+ var deepestDescriptor: CallableMemberDescriptor = descriptor
+ while (!deepestDescriptor.overriddenDescriptors.isEmpty()) {
+ deepestDescriptor = deepestDescriptor.overriddenDescriptors.first()
+ }
+ if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") {
+ val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassDescriptors(
+ FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.FROM_IDE)
+ anyClassDescriptors.forEach {
+ val anyMethod = it.getMemberScope(listOf())
+ .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS, { it == descriptor.name })
+ .single()
+ val kdoc = KDocFinder.findKDoc(anyMethod)
+ if (kdoc != null) {
+ return kdoc
+ }
+ }
+ }
+ }
+ return null
+ }
+
+ 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).parseDocumentation(psi as PsiNamedElement)
+ return parseResult.content to { node ->
+ parseResult.deprecatedContent?.let {
+ val deprecationNode = DocumentationNode("", it, DocumentationNode.Kind.Modifier)
+ node.append(deprecationNode, DocumentationReference.Kind.Deprecation)
+ }
+ }
+ }
+ return Content.Empty to { node -> }
+ }
+
+ fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, KDocTag::class.java) ?: arrayOf()
+
+ private fun MutableContent.addTagToSeeAlso(descriptor: DeclarationDescriptor, seeTag: KDocTag) {
+ val subjectName = seeTag.getSubjectName()
+ if (subjectName != null) {
+ val seeSection = findSectionByTag("See Also") ?: addSection("See Also", null)
+ val link = linkResolver.resolveContentLink(descriptor, subjectName)
+ link.append(ContentText(subjectName))
+ val para = ContentParagraph()
+ para.append(link)
+ seeSection.append(para)
+ }
+ }
+
+ private fun functionBody(descriptor: DeclarationDescriptor, functionName: String?): ContentNode {
+ if (functionName == null) {
+ logger.warn("Missing function name in @sample in ${descriptor.signature()}")
+ return ContentBlockCode().let() { it.append(ContentText("Missing function name in @sample")); it }
+ }
+ val scope = getResolutionScope(resolutionFacade, descriptor)
+ val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT)
+ val rootScope = rootPackage.memberScope
+ val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope)
+ if (symbol == null) {
+ logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}")
+ return ContentBlockCode().let() { it.append(ContentText("Unresolved: $functionName")); it }
+ }
+ val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol)
+ if (psiElement == null) {
+ logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}")
+ return ContentBlockCode().let() { it.append(ContentText("Source not found: $functionName")); it }
+ }
+
+ val text = when (psiElement) {
+ is KtDeclarationWithBody -> ContentBlockCode().let() {
+ val bodyExpression = psiElement.bodyExpression
+ when (bodyExpression) {
+ is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
+ else -> bodyExpression!!.text
+ }
+ }
+ else -> psiElement.text
+ }
+
+ val lines = text.trimEnd().split("\n".toRegex()).toTypedArray().filterNot { it.length == 0 }
+ val indent = lines.map { it.takeWhile { it.isWhitespace() }.count() }.min() ?: 0
+ val finalText = lines.map { it.drop(indent) }.joinToString("\n")
+ return ContentBlockCode("kotlin").let() { it.append(ContentText(finalText)); it }
+ }
+
+ private fun resolveInScope(functionName: String, scope: ResolutionScope): DeclarationDescriptor? {
+ var currentScope = scope
+ val parts = functionName.split('.')
+
+ var symbol: DeclarationDescriptor? = null
+
+ for (part in parts) {
+ // short name
+ val symbolName = Name.guess(part)
+ val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL, { it == symbolName })
+ .filter { it.name == symbolName }
+ .firstOrNull()
+
+ if (partSymbol == null) {
+ symbol = null
+ break
+ }
+ currentScope = if (partSymbol is ClassDescriptor)
+ partSymbol.defaultType.memberScope
+ else
+ getResolutionScope(resolutionFacade, partSymbol)
+ symbol = partSymbol
+ }
+
+ return symbol
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
new file mode 100644
index 00000000..6551ded6
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -0,0 +1,653 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.PsiJavaFile
+import org.jetbrains.dokka.DocumentationNode.Kind
+import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.descriptors.*
+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.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
+import org.jetbrains.kotlin.idea.kdoc.KDocFinder
+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.FqName
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
+import org.jetbrains.kotlin.resolve.constants.ConstantValue
+import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isDocumentedAnnotation
+import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+import org.jetbrains.kotlin.resolve.source.getPsi
+import org.jetbrains.kotlin.types.ErrorUtils
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeProjection
+
+public data class DocumentationOptions(val outputDir: String,
+ val outputFormat: String,
+ val includeNonPublic: Boolean = false,
+ val reportUndocumented: Boolean = true,
+ val skipEmptyPackages: Boolean = true,
+ val skipDeprecated: Boolean = false,
+ val sourceLinks: List<SourceLinkDefinition>)
+
+private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: DeclarationDescriptor): Boolean {
+ val package1 = DescriptorUtils.getParentOfType(descriptor1, PackageFragmentDescriptor::class.java)
+ val package2 = DescriptorUtils.getParentOfType(descriptor2, PackageFragmentDescriptor::class.java)
+ return package1 != null && package2 != null && package1.fqName == package2.fqName
+}
+
+interface PackageDocumentationBuilder {
+ fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>)
+}
+
+class DocumentationBuilder
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val descriptorDocumentationParser: DescriptorDocumentationParser,
+ val options: DocumentationOptions,
+ val refGraph: NodeReferenceGraph,
+ val logger: DokkaLogger)
+{
+ val visibleToDocumentation = setOf(Visibilities.PROTECTED, Visibilities.PUBLIC)
+ val boringBuiltinClasses = setOf(
+ "kotlin.Unit", "kotlin.Byte", "kotlin.Short", "kotlin.Int", "kotlin.Long", "kotlin.Char", "kotlin.Boolean",
+ "kotlin.Float", "kotlin.Double", "kotlin.String", "kotlin.Array", "kotlin.Any")
+ val knownModifiers = setOf(
+ KtTokens.PUBLIC_KEYWORD, KtTokens.PROTECTED_KEYWORD, KtTokens.INTERNAL_KEYWORD, KtTokens.PRIVATE_KEYWORD,
+ KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD,
+ KtTokens.OVERRIDE_KEYWORD)
+
+ fun link(node: DocumentationNode, descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) {
+ refGraph.link(node, descriptor.signature(), kind)
+ }
+
+ fun link(fromDescriptor: DeclarationDescriptor?, toDescriptor: DeclarationDescriptor?, kind: DocumentationReference.Kind) {
+ if (fromDescriptor != null && toDescriptor != null) {
+ refGraph.link(fromDescriptor.signature(), toDescriptor.signature(), kind)
+ }
+ }
+
+ fun register(descriptor: DeclarationDescriptor, node: DocumentationNode) {
+ refGraph.register(descriptor.signature(), node)
+ }
+
+ fun <T> nodeForDescriptor(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named {
+ val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor, kind == Kind.Parameter)
+ val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor)
+ callback(node)
+ return node
+ }
+
+ private fun DocumentationNode.withModifiers(descriptor: DeclarationDescriptor) : DocumentationNode{
+ if (descriptor is MemberDescriptor) {
+ appendVisibility(descriptor)
+ if (descriptor !is ConstructorDescriptor) {
+ appendModality(descriptor)
+ }
+ }
+ return this
+ }
+
+ fun DocumentationNode.appendModality(descriptor: MemberDescriptor) {
+ var modality = descriptor.modality
+ if (modality == Modality.OPEN) {
+ val containingClass = descriptor.containingDeclaration as? ClassDescriptor
+ if (containingClass?.modality == Modality.FINAL) {
+ modality = Modality.FINAL
+ }
+ }
+ val modifier = modality.name.toLowerCase()
+ appendTextNode(modifier, DocumentationNode.Kind.Modifier)
+ }
+
+ fun DocumentationNode.appendVisibility(descriptor: DeclarationDescriptorWithVisibility) {
+ val modifier = descriptor.visibility.normalize().displayName
+ appendTextNode(modifier, DocumentationNode.Kind.Modifier)
+ }
+
+ fun DocumentationNode.appendSupertypes(descriptor: ClassDescriptor) {
+ val superTypes = descriptor.typeConstructor.supertypes
+ for (superType in superTypes) {
+ if (!ignoreSupertype(superType)) {
+ appendType(superType, DocumentationNode.Kind.Supertype)
+ val superclass = superType?.constructor?.declarationDescriptor
+ link(superclass, descriptor, DocumentationReference.Kind.Inheritor)
+ link(descriptor, superclass, DocumentationReference.Kind.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: DocumentationNode.Kind = DocumentationNode.Kind.Type) {
+ if (projection.isStarProjection) {
+ appendTextNode("*", Kind.Type)
+ }
+ else {
+ appendType(projection.type, kind, projection.projectionKind.label)
+ }
+ }
+
+ fun DocumentationNode.appendType(kotlinType: KotlinType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type, prefix: String = "") {
+ if (kotlinType == null)
+ return
+ val classifierDescriptor = kotlinType.constructor.declarationDescriptor
+ val name = when (classifierDescriptor) {
+ is ClassDescriptor -> {
+ if (classifierDescriptor.isCompanionObject) {
+ classifierDescriptor.containingDeclaration.name.asString() +
+ "." + classifierDescriptor.name.asString()
+ }
+ else {
+ classifierDescriptor.name.asString()
+ }
+ }
+ is Named -> classifierDescriptor.name.asString()
+ else -> "<anonymous>"
+ }
+ val node = DocumentationNode(name, Content.Empty, kind)
+ if (prefix != "") {
+ node.appendTextNode(prefix, Kind.Modifier)
+ }
+ if (kotlinType.isMarkedNullable) {
+ node.appendTextNode("?", Kind.NullabilityModifier)
+ }
+ if (classifierDescriptor != null) {
+ link(node, classifierDescriptor,
+ if (classifierDescriptor.isBoringBuiltinClass()) DocumentationReference.Kind.HiddenLink else DocumentationReference.Kind.Link)
+ }
+
+ append(node, DocumentationReference.Kind.Detail)
+ node.appendAnnotations(kotlinType)
+ for (typeArgument in kotlinType.arguments) {
+ node.appendProjection(typeArgument)
+ }
+ }
+
+ fun ClassifierDescriptor.isBoringBuiltinClass(): Boolean =
+ DescriptorUtils.getFqName(this).asString() in boringBuiltinClasses
+
+ fun DocumentationNode.appendAnnotations(annotated: Annotated) {
+ annotated.annotations.filter { it.isDocumented() }.forEach {
+ val annotationNode = it.build()
+ if (annotationNode != null) {
+ append(annotationNode,
+ if (annotationNode.isDeprecation()) DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation)
+ }
+ }
+ }
+
+ fun DocumentationNode.appendModifiers(descriptor: DeclarationDescriptor) {
+ val psi = (descriptor as DeclarationDescriptorWithSource).source.getPsi() as? KtModifierListOwner ?: return
+ KtTokens.MODIFIER_KEYWORDS_ARRAY.filter { it !in knownModifiers }.forEach {
+ if (psi.hasModifier(it)) {
+ appendTextNode(it.value, Kind.Modifier)
+ }
+ }
+ }
+
+ fun DocumentationNode.isDeprecation() = name == "Deprecated" || name == "deprecated"
+
+ fun DocumentationNode.appendSourceLink(sourceElement: SourceElement) {
+ appendSourceLink(sourceElement.getPsi(), options.sourceLinks)
+ }
+
+ fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind): DocumentationNode? {
+ // do not include generated code
+ if (descriptor is CallableMemberDescriptor && descriptor.kind != CallableMemberDescriptor.Kind.DECLARATION)
+ return null
+
+ if (descriptor.isDocumented()) {
+ val node = descriptor.build()
+ append(node, kind)
+ return node
+ }
+ return null
+ }
+
+ fun DeclarationDescriptor.isDocumented(): Boolean {
+ return (options.includeNonPublic
+ || this !is MemberDescriptor
+ || this.visibility in visibleToDocumentation) &&
+ !isDocumentationSuppressed() &&
+ (!options.skipDeprecated || !isDeprecated())
+ }
+
+ fun DocumentationNode.appendMembers(descriptors: Iterable<DeclarationDescriptor>): List<DocumentationNode> {
+ val nodes = descriptors.map { descriptor ->
+ if (descriptor is CallableMemberDescriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
+ val baseDescriptor = descriptor.overriddenDescriptors.firstOrNull()
+ if (baseDescriptor != null) {
+ link(this, baseDescriptor, DocumentationReference.Kind.InheritedMember)
+ }
+ null
+ }
+ else {
+ val descriptorToUse = if (descriptor is ConstructorDescriptor) descriptor else descriptor.original
+ appendChild(descriptorToUse, DocumentationReference.Kind.Member)
+ }
+ }
+ return nodes.filterNotNull()
+ }
+
+ fun DocumentationNode.appendInPageChildren(descriptors: Iterable<DeclarationDescriptor>, kind: DocumentationReference.Kind) {
+ descriptors.forEach { descriptor ->
+ val node = appendChild(descriptor, kind)
+ node?.addReferenceTo(this, DocumentationReference.Kind.TopLevelPage)
+ }
+ }
+
+ fun DocumentationModule.appendFragments(fragments: Collection<PackageFragmentDescriptor>,
+ packageContent: Map<String, Content>,
+ packageDocumentationBuilder: PackageDocumentationBuilder) {
+ val allFqNames = fragments.map { it.fqName }.distinct()
+
+ for (packageName in allFqNames) {
+ val declarations = fragments.filter { it.fqName == packageName }.flatMap { it.getMemberScope().getContributedDescriptors() }
+
+ if (options.skipEmptyPackages && declarations.none { it.isDocumented() }) continue
+ logger.info(" package $packageName: ${declarations.count()} declarations")
+ val packageNode = findOrCreatePackageNode(packageName.asString(), packageContent)
+ packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode, declarations)
+ }
+ }
+
+ fun DeclarationDescriptor.build(): DocumentationNode = when (this) {
+ is ClassDescriptor -> build()
+ is ConstructorDescriptor -> build()
+ is PropertyDescriptor -> build()
+ is FunctionDescriptor -> build()
+ is TypeParameterDescriptor -> build()
+ is ValueParameterDescriptor -> build()
+ is ReceiverParameterDescriptor -> build()
+ else -> throw IllegalStateException("Descriptor $this is not known")
+ }
+
+ fun ClassDescriptor.build(): DocumentationNode {
+ val kind = when (kind) {
+ ClassKind.OBJECT -> Kind.Object
+ ClassKind.INTERFACE -> Kind.Interface
+ ClassKind.ENUM_CLASS -> Kind.Enum
+ ClassKind.ANNOTATION_CLASS -> Kind.AnnotationClass
+ ClassKind.ENUM_ENTRY -> Kind.EnumItem
+ else -> Kind.Class
+ }
+ val node = nodeForDescriptor(this, kind)
+ node.appendSupertypes(this)
+ if (getKind() != ClassKind.OBJECT && getKind() != ClassKind.ENUM_ENTRY) {
+ node.appendInPageChildren(typeConstructor.parameters, DocumentationReference.Kind.Detail)
+ val constructorsToDocument = if (getKind() == ClassKind.ENUM_CLASS)
+ constructors.filter { it.valueParameters.size > 0 }
+ else
+ constructors
+ node.appendMembers(constructorsToDocument)
+ }
+ val members = defaultType.memberScope.getContributedDescriptors().filter { it != companionObjectDescriptor }
+ node.appendMembers(members)
+ node.appendMembers(staticScope.getContributedDescriptors()).forEach {
+ it.appendTextNode("static", Kind.Modifier)
+ }
+ val companionObjectDescriptor = companionObjectDescriptor
+ if (companionObjectDescriptor != null) {
+ node.appendMembers(companionObjectDescriptor.defaultType.memberScope.getContributedDescriptors())
+ }
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+ register(this, node)
+ return node
+ }
+
+ fun ConstructorDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, Kind.Constructor)
+ node.appendInPageChildren(valueParameters, DocumentationReference.Kind.Detail)
+ register(this, node)
+ return node
+ }
+
+ private fun CallableMemberDescriptor.inCompanionObject(): Boolean {
+ val containingDeclaration = containingDeclaration
+ if ((containingDeclaration as? ClassDescriptor)?.isCompanionObject ?: false) {
+ return true
+ }
+ val receiver = extensionReceiverParameter
+ return (receiver?.type?.constructor?.declarationDescriptor as? ClassDescriptor)?.isCompanionObject ?: false
+ }
+
+ fun FunctionDescriptor.build(): DocumentationNode {
+ if (ErrorUtils.containsErrorType(this)) {
+ logger.warn("Found an unresolved type in ${signatureWithSourceLocation()}")
+ }
+
+ val node = nodeForDescriptor(this, if (inCompanionObject()) Kind.CompanionObjectFunction else Kind.Function)
+
+ node.appendInPageChildren(typeParameters, DocumentationReference.Kind.Detail)
+ extensionReceiverParameter?.let { node.appendChild(it, DocumentationReference.Kind.Detail) }
+ node.appendInPageChildren(valueParameters, DocumentationReference.Kind.Detail)
+ node.appendType(returnType)
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+
+ overriddenDescriptors.forEach {
+ addOverrideLink(it, this)
+ }
+
+ register(this, node)
+ return node
+ }
+
+ fun addOverrideLink(baseClassFunction: CallableMemberDescriptor, overridingFunction: CallableMemberDescriptor) {
+ val source = baseClassFunction.original.source.getPsi()
+ if (source != null) {
+ link(overridingFunction, baseClassFunction, DocumentationReference.Kind.Override)
+ } else {
+ baseClassFunction.overriddenDescriptors.forEach {
+ addOverrideLink(it, overridingFunction)
+ }
+ }
+ }
+
+ fun PropertyDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, if (inCompanionObject()) Kind.CompanionObjectProperty else Kind.Property)
+ node.appendInPageChildren(typeParameters, DocumentationReference.Kind.Detail)
+ extensionReceiverParameter?.let { node.appendChild(it, DocumentationReference.Kind.Detail) }
+ node.appendType(returnType)
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ node.appendSourceLink(source)
+ if (isVar) {
+ node.appendTextNode("var", DocumentationNode.Kind.Modifier)
+ }
+ getter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter")
+ }
+ }
+ setter?.let {
+ if (!it.isDefault) {
+ node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter")
+ }
+ }
+
+ overriddenDescriptors.forEach {
+ addOverrideLink(it, this)
+ }
+
+ register(this, node)
+ return node
+ }
+
+ fun DocumentationNode.addAccessorDocumentation(documentation: Content, prefix: String) {
+ if (documentation == Content.Empty) return
+ updateContent {
+ if (!documentation.children.isEmpty()) {
+ val section = addSection(prefix, null)
+ documentation.children.forEach { section.append(it) }
+ }
+ documentation.sections.forEach {
+ val section = addSection("$prefix ${it.tag}", it.subjectName)
+ it.children.forEach { section.append(it) }
+ }
+ }
+ }
+
+ fun ValueParameterDescriptor.build(): DocumentationNode {
+ val node = nodeForDescriptor(this, Kind.Parameter)
+ node.appendType(varargElementType ?: type)
+ if (declaresDefaultValue()) {
+ val psi = source.getPsi() as? KtParameter
+ if (psi != null) {
+ val defaultValueText = psi.defaultValue?.text
+ if (defaultValueText != null) {
+ node.appendTextNode(defaultValueText, Kind.Value)
+ }
+ }
+ }
+ node.appendAnnotations(this)
+ node.appendModifiers(this)
+ if (varargElementType != null && node.details(Kind.Modifier).none { it.name == "vararg" }) {
+ node.appendTextNode("vararg", Kind.Modifier)
+ }
+ register(this, node)
+ return node
+ }
+
+ fun TypeParameterDescriptor.build(): DocumentationNode {
+ val doc = descriptorDocumentationParser.parseDocumentation(this)
+ val name = name.asString()
+ val prefix = variance.label
+
+ val node = DocumentationNode(name, doc, DocumentationNode.Kind.TypeParameter)
+ if (prefix != "") {
+ node.appendTextNode(prefix, Kind.Modifier)
+ }
+ if (isReified) {
+ node.appendTextNode("reified", Kind.Modifier)
+ }
+
+ for (constraint in upperBounds) {
+ if (KotlinBuiltIns.isDefaultBound(constraint)) {
+ continue
+ }
+ node.appendType(constraint, Kind.UpperBound)
+ }
+
+ for (constraint in lowerBounds) {
+ if (KotlinBuiltIns.isNothing(constraint))
+ continue
+ node.appendType(constraint, Kind.LowerBound)
+ }
+ return node
+ }
+
+ fun ReceiverParameterDescriptor.build(): DocumentationNode {
+ var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!!
+ if ((receiverClass as? ClassDescriptor)?.isCompanionObject ?: false) {
+ receiverClass = receiverClass.containingDeclaration!!
+ }
+ link(receiverClass,
+ containingDeclaration,
+ DocumentationReference.Kind.Extension)
+
+ val node = DocumentationNode(name.asString(), Content.Empty, Kind.Receiver)
+ node.appendType(type)
+ return node
+ }
+
+ fun AnnotationDescriptor.build(): DocumentationNode? {
+ val annotationClass = type.constructor.declarationDescriptor
+ if (annotationClass == null || ErrorUtils.isError(annotationClass)) {
+ return null
+ }
+ val node = DocumentationNode(annotationClass.name.asString(), Content.Empty, DocumentationNode.Kind.Annotation)
+ val arguments = allValueArguments.toList().sortedBy { it.first.index }
+ arguments.forEach {
+ val valueNode = it.second.toDocumentationNode()
+ if (valueNode != null) {
+ val paramNode = DocumentationNode(it.first.name.asString(), Content.Empty, DocumentationNode.Kind.Parameter)
+ paramNode.append(valueNode, DocumentationReference.Kind.Detail)
+ node.append(paramNode, DocumentationReference.Kind.Detail)
+ }
+ }
+ return node
+ }
+
+ fun CompileTimeConstant<Any?>.build(): DocumentationNode? = when (this) {
+ is TypedCompileTimeConstant -> constantValue.toDocumentationNode()
+ else -> null
+ }
+
+ fun ConstantValue<*>.toDocumentationNode(): DocumentationNode? = value?.let { value ->
+ when (value) {
+ is String ->
+ "\"" + StringUtil.escapeStringCharacters(value) + "\""
+ is EnumEntrySyntheticClassDescriptor ->
+ value.containingDeclaration.name.asString() + "." + value.name.asString()
+ else -> value.toString()
+ }.let { valueString ->
+ DocumentationNode(valueString, Content.Empty, DocumentationNode.Kind.Value)
+ }
+ }
+}
+
+class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder {
+ override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>) {
+ val externalClassNodes = hashMapOf<FqName, DocumentationNode>()
+ declarations.forEach { descriptor ->
+ with(documentationBuilder) {
+ if (descriptor.isDocumented()) {
+ val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes)
+ parent.appendChild(descriptor, DocumentationReference.Kind.Member)
+ }
+ }
+ }
+ }
+}
+
+class KotlinJavaDocumentationBuilder
+ @Inject constructor(val documentationBuilder: DocumentationBuilder,
+ val logger: DokkaLogger) : JavaDocumentationBuilder
+{
+ override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
+ val packageNode = module.findOrCreatePackageNode(file.packageName, packageContent)
+
+ file.classes.forEach {
+ val javaDescriptorResolver = KotlinCacheService.getInstance(file.project).getProjectService(JvmPlatform,
+ it.getModuleInfo(), JavaDescriptorResolver::class.java)
+
+ val descriptor = javaDescriptorResolver.resolveClass(JavaClassImpl(it))
+ if (descriptor == null) {
+ logger.warn("Cannot find descriptor for Java class ${it.qualifiedName}")
+ }
+ else {
+ with(documentationBuilder) {
+ packageNode.appendChild(descriptor, DocumentationReference.Kind.Member)
+ }
+ }
+ }
+ }
+}
+
+private fun AnnotationDescriptor.isDocumented(): Boolean {
+ if (source.getPsi() != null && mustBeDocumented()) return true
+ val annotationClassName = type.constructor.declarationDescriptor?.fqNameSafe?.asString()
+ return annotationClassName == "kotlin.Extension"
+}
+
+fun AnnotationDescriptor.mustBeDocumented(): Boolean {
+ val annotationClass = type.constructor.declarationDescriptor as? Annotated ?: return false
+ return annotationClass.isDocumentedAnnotation()
+}
+
+fun DeclarationDescriptor.isDocumentationSuppressed(): Boolean {
+ val doc = KDocFinder.findKDoc(this)
+ return doc is KDocSection && doc.findTagByName("suppress") != null
+}
+
+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>): DocumentationNode {
+ if (descriptor is CallableMemberDescriptor) {
+ val extensionClassDescriptor = descriptor.getExtensionClassDescriptor()
+ if (extensionClassDescriptor != null && !isSamePackage(descriptor, extensionClassDescriptor) &&
+ !ErrorUtils.isError(extensionClassDescriptor)) {
+ val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor)
+ return externalClassNodes.getOrPut(fqName, {
+ val newNode = DocumentationNode(fqName.asString(), Content.Empty, Kind.ExternalClass)
+ append(newNode, DocumentationReference.Kind.Member)
+ newNode
+ })
+ }
+ }
+ return this
+}
+
+fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? {
+ val extensionReceiver = extensionReceiverParameter
+ if (extensionReceiver != null) {
+ val type = extensionReceiver.type
+ return type.constructor.declarationDescriptor as? ClassDescriptor
+ }
+ return null
+}
+
+fun DeclarationDescriptor.signature(): String = when(this) {
+ is ClassDescriptor, is PackageFragmentDescriptor -> DescriptorUtils.getFqName(this).asString()
+ is PropertyDescriptor -> containingDeclaration.signature() + "#" + name + receiverSignature()
+ is FunctionDescriptor -> containingDeclaration.signature() + "#" + name + parameterSignature()
+ is ValueParameterDescriptor -> containingDeclaration.signature() + ":" + name
+ is TypeParameterDescriptor -> containingDeclaration.signature() + "<" + name
+
+ else -> throw UnsupportedOperationException("Don't know how to calculate signature for $this")
+}
+
+fun PropertyDescriptor.receiverSignature(): String {
+ val receiver = extensionReceiverParameter
+ if (receiver != null) {
+ return "#" + receiver.type.signature()
+ }
+ return ""
+}
+
+fun CallableMemberDescriptor.parameterSignature(): String {
+ val params = valueParameters.map { it.type }.toArrayList()
+ val extensionReceiver = extensionReceiverParameter
+ if (extensionReceiver != null) {
+ params.add(0, extensionReceiver.type)
+ }
+ return "(" + params.map { it.signature() }.joinToString() + ")"
+}
+
+fun KotlinType.signature(): String {
+ val declarationDescriptor = constructor.declarationDescriptor ?: return "<null>"
+ val typeName = DescriptorUtils.getFqName(declarationDescriptor).asString()
+ if (typeName == "Array" && arguments.size == 1) {
+ return "Array<" + arguments.first().type.signature() + ">"
+ }
+ return typeName
+}
+
+fun DeclarationDescriptor.signatureWithSourceLocation(): String {
+ val signature = signature()
+ val sourceLocation = sourceLocation()
+ return if (sourceLocation != null) "$signature ($sourceLocation)" else signature
+}
+
+fun DeclarationDescriptor.sourceLocation(): String? {
+ if (this is DeclarationDescriptorWithSource) {
+ val psi = (this.source as? PsiSourceElement)?.getPsi()
+ if (psi != null) {
+ val fileName = psi.containingFile.name
+ val lineNumber = psi.lineNumber()
+ return if (lineNumber != null) "$fileName:$lineNumber" else fileName
+ }
+ }
+ return null
+}
diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
new file mode 100644
index 00000000..7a1d591c
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt
@@ -0,0 +1,64 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
+import org.jetbrains.kotlin.asJava.KtLightElement
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
+
+class KotlinAsJavaDocumentationBuilder
+ @Inject constructor(val kotlinAsJavaDocumentationParser: KotlinAsJavaDocumentationParser) : PackageDocumentationBuilder
+{
+ override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder,
+ packageName: FqName,
+ packageNode: DocumentationNode,
+ declarations: List<DeclarationDescriptor>) {
+ val project = documentationBuilder.resolutionFacade.project
+ val psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName.asString())
+ if (psiPackage == null) {
+ documentationBuilder.logger.error("Cannot find Java package by qualified name: ${packageName.asString()}")
+ return
+ }
+
+ val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options,
+ documentationBuilder.refGraph,
+ kotlinAsJavaDocumentationParser)
+
+ psiPackage.classes.filter { it is KtLightElement<*, *> }.filter { it.isVisibleInDocumentation() }.forEach {
+ javaDocumentationBuilder.appendClasses(packageNode, arrayOf(it))
+ }
+ }
+
+ fun PsiClass.isVisibleInDocumentation() : Boolean {
+ val origin: KtDeclaration? = (this as KtLightElement<*, *>).getOrigin()
+ return origin?.hasModifier(KtTokens.INTERNAL_KEYWORD) != true &&
+ origin?.hasModifier(KtTokens.PRIVATE_KEYWORD) != true
+ }
+}
+
+class KotlinAsJavaDocumentationParser
+ @Inject constructor(val resolutionFacade: DokkaResolutionFacade,
+ val descriptorDocumentationParser: DescriptorDocumentationParser) : JavaDocumentationParser
+{
+ override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult {
+ val kotlinLightElement = element as? KtLightElement<*, *> ?: return JavadocParseResult.Empty
+ val origin = kotlinLightElement.getOrigin() ?: return JavadocParseResult.Empty
+ if (origin is KtParameter) {
+ // LazyDeclarationResolver does not support setter parameters
+ val grandFather = origin.parent?.parent
+ if (grandFather is KtPropertyAccessor) {
+ return JavadocParseResult.Empty
+ }
+ }
+ val descriptor = resolutionFacade.resolveToDescriptor(origin)
+ val content = descriptorDocumentationParser.parseDocumentation(descriptor, origin is KtParameter)
+ return JavadocParseResult(content, null)
+ }
+}
diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
new file mode 100644
index 00000000..0d39f410
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
@@ -0,0 +1,409 @@
+package org.jetbrains.dokka
+
+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")
+
+ override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode {
+ return content {
+ when (node.kind) {
+ DocumentationNode.Kind.Package -> if (renderMode == RenderMode.FULL) renderPackage(node)
+ in DocumentationNode.Kind.classLike -> renderClass(node, renderMode)
+
+ DocumentationNode.Kind.EnumItem,
+ DocumentationNode.Kind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name)
+
+ DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node, renderMode)
+ DocumentationNode.Kind.Type,
+ DocumentationNode.Kind.UpperBound -> renderType(node, renderMode)
+
+ DocumentationNode.Kind.Modifier -> renderModifier(node)
+ DocumentationNode.Kind.Constructor,
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectFunction -> renderFunction(node, renderMode)
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.CompanionObjectProperty -> renderProperty(node, renderMode)
+ else -> identifier(node.name)
+ }
+ }
+ }
+
+ override fun renderName(node: DocumentationNode): String {
+ return when (node.kind) {
+ DocumentationNode.Kind.Constructor -> node.owner!!.name
+ else -> node.name
+ }
+ }
+
+ override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? {
+ if (nodes.size < 2) return null
+ val receiverKind = nodes.getReceiverKind() ?: return null
+ val functionWithTypeParameter = nodes.firstOrNull { it.details(DocumentationNode.Kind.TypeParameter).any() } ?: return null
+ return content {
+ val typeParameter = functionWithTypeParameter.details(DocumentationNode.Kind.TypeParameter).first()
+ if (functionWithTypeParameter.kind == DocumentationNode.Kind.Function) {
+ renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
+ }
+ else {
+ renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
+ }
+ }
+ }
+
+ private fun List<DocumentationNode>.getReceiverKind(): ReceiverKind? {
+ val qNames = map { it.getReceiverQName() }.filterNotNull()
+ if (qNames.size != size)
+ return null
+
+ return ReceiverKind.values.firstOrNull { kind -> qNames.all { it in kind.classes } }
+ }
+
+ private fun DocumentationNode.getReceiverQName(): String? {
+ if (kind != DocumentationNode.Kind.Function && kind != DocumentationNode.Kind.Property) return null
+ val receiver = details(DocumentationNode.Kind.Receiver).singleOrNull() ?: return null
+ return receiver.detail(DocumentationNode.Kind.Type).qualifiedNameFromType()
+ }
+
+ companion object {
+ private val arrayClasses = setOf(
+ "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
+ }
+
+ private enum class ReceiverKind(val receiverName: String, val classes: Collection<String>) {
+ ARRAY("any_array", arrayClasses),
+ ARRAY_OR_LIST("any_array_or_list", arrayOrListClasses),
+ ITERABLE("any_iterable", iterableClasses),
+ }
+
+ interface SignatureMapper {
+ fun renderReceiver(receiver: DocumentationNode, to: ContentBlock)
+ }
+
+ 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 ContentBlock.renderList(nodes: List<DocumentationNode>, separator: String = ", ",
+ noWrap: Boolean = false, renderItem: (DocumentationNode) -> 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.renderType(node: DocumentationNode, renderMode: RenderMode) {
+ var typeArguments = node.details(DocumentationNode.Kind.Type)
+ if (node.name == "Function${typeArguments.count() - 1}") {
+ // lambda
+ val isExtension = node.annotations.any { it.name == "Extension" }
+ if (isExtension) {
+ renderType(typeArguments.first(), renderMode)
+ symbol(".")
+ typeArguments = typeArguments.drop(1)
+ }
+ symbol("(")
+ renderList(typeArguments.take(typeArguments.size - 1), noWrap = true) {
+ renderType(it, renderMode)
+ }
+ symbol(")")
+ nbsp()
+ symbol("->")
+ nbsp()
+ renderType(typeArguments.last(), renderMode)
+ return
+ }
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode, true)
+ renderLinked(node) { identifier(it.name, IdentifierKind.TypeName) }
+ if (typeArguments.any()) {
+ symbol("<")
+ renderList(typeArguments, noWrap = true) {
+ renderType(it, renderMode)
+ }
+ symbol(">")
+ }
+ val nullabilityModifier = node.details(DocumentationNode.Kind.NullabilityModifier).singleOrNull()
+ if (nullabilityModifier != null) {
+ symbol(nullabilityModifier.name)
+ }
+ }
+
+ private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) {
+ when (node.name) {
+ "final", "public", "var" -> {}
+ else -> {
+ keyword(node.name)
+ if (nowrap) {
+ nbsp()
+ }
+ else {
+ text(" ")
+ }
+ }
+ }
+ }
+
+ private fun ContentBlock.renderTypeParameter(node: DocumentationNode, renderMode: RenderMode) {
+ renderModifiersForNode(node, renderMode, true)
+
+ identifier(node.name)
+
+ val constraints = node.details(DocumentationNode.Kind.UpperBound)
+ if (constraints.any()) {
+ nbsp()
+ symbol(":")
+ nbsp()
+ renderList(constraints, noWrap=true) {
+ renderType(it, renderMode)
+ }
+ }
+ }
+ private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ identifier(node.name, IdentifierKind.ParameterName)
+ symbol(":")
+ nbsp()
+ val parameterType = node.detail(DocumentationNode.Kind.Type)
+ renderType(parameterType, renderMode)
+ val valueNode = node.details(DocumentationNode.Kind.Value).firstOrNull()
+ if (valueNode != null) {
+ nbsp()
+ symbol("=")
+ nbsp()
+ text(valueNode.name)
+ }
+ }
+
+ private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode, renderMode: RenderMode) {
+ val typeParameters = node.details(DocumentationNode.Kind.TypeParameter)
+ if (typeParameters.any()) {
+ symbol("<")
+ renderList(typeParameters) {
+ renderTypeParameter(it, renderMode)
+ }
+ symbol(">")
+ }
+ }
+
+ private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) {
+ val supertypes = node.details(DocumentationNode.Kind.Supertype)
+ if (supertypes.any()) {
+ nbsp()
+ symbol(":")
+ nbsp()
+ renderList(supertypes) {
+ indentedSoftLineBreak()
+ renderType(it, renderMode)
+ }
+ }
+ }
+
+ private fun ContentBlock.renderModifiersForNode(node: DocumentationNode,
+ renderMode: RenderMode,
+ nowrap: Boolean = false) {
+ val modifiers = node.details(DocumentationNode.Kind.Modifier)
+ for (it in modifiers) {
+ if (node.kind == org.jetbrains.dokka.DocumentationNode.Kind.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)
+ }
+ }
+
+ private fun ContentBlock.renderAnnotation(node: DocumentationNode) {
+ identifier("@" + node.name, IdentifierKind.AnnotationName)
+ val parameters = node.details(DocumentationNode.Kind.Parameter)
+ if (!parameters.isEmpty()) {
+ symbol("(")
+ renderList(parameters) {
+ text(it.detail(DocumentationNode.Kind.Value).name)
+ }
+ symbol(")")
+ }
+ text(" ")
+ }
+
+ private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.AnnotationClass,
+ DocumentationNode.Kind.Enum -> keyword("class ")
+ DocumentationNode.Kind.Interface -> keyword("interface ")
+ DocumentationNode.Kind.EnumItem -> keyword("enum val ")
+ DocumentationNode.Kind.Object -> keyword("object ")
+ else -> throw IllegalArgumentException("Node $node is not a class-like object")
+ }
+
+ identifierOrDeprecated(node)
+ renderTypeParametersForNode(node, renderMode)
+ renderSupertypesForNode(node, renderMode)
+ }
+
+ private fun ContentBlock.renderFunction(node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Constructor -> identifier(node.owner!!.name)
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectFunction -> keyword("fun ")
+ else -> throw IllegalArgumentException("Node $node is not a function-like object")
+ }
+ renderTypeParametersForNode(node, renderMode)
+ if (node.details(DocumentationNode.Kind.TypeParameter).any()) {
+ text(" ")
+ }
+
+ renderReceiver(node, renderMode, signatureMapper)
+
+ if (node.kind != org.jetbrains.dokka.DocumentationNode.Kind.Constructor)
+ identifierOrDeprecated(node)
+
+ symbol("(")
+ val parameters = node.details(DocumentationNode.Kind.Parameter)
+ renderList(parameters) {
+ indentedSoftLineBreak()
+ renderParameter(it, renderMode)
+ }
+ if (needReturnType(node)) {
+ if (parameters.isNotEmpty()) {
+ softLineBreak()
+ }
+ symbol(")")
+ symbol(": ")
+ renderType(node.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+ else {
+ symbol(")")
+ }
+ }
+
+ private fun ContentBlock.renderReceiver(node: DocumentationNode, renderMode: RenderMode, signatureMapper: SignatureMapper?) {
+ val receiver = node.details(DocumentationNode.Kind.Receiver).singleOrNull()
+ if (receiver != null) {
+ if (signatureMapper != null) {
+ signatureMapper.renderReceiver(receiver, this)
+ } else {
+ renderType(receiver.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+ symbol(".")
+ }
+ }
+
+ private fun needReturnType(node: DocumentationNode) = when(node.kind) {
+ DocumentationNode.Kind.Constructor -> false
+ else -> !node.isUnitReturnType()
+ }
+
+ fun DocumentationNode.isUnitReturnType(): Boolean =
+ detail(DocumentationNode.Kind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit"
+
+ private fun ContentBlock.renderProperty(node: DocumentationNode,
+ renderMode: RenderMode,
+ signatureMapper: SignatureMapper? = null) {
+ if (renderMode == RenderMode.FULL) {
+ renderAnnotationsForNode(node)
+ }
+ renderModifiersForNode(node, renderMode)
+ when (node.kind) {
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.CompanionObjectProperty -> keyword("${node.getPropertyKeyword()} ")
+ else -> throw IllegalArgumentException("Node $node is not a property")
+ }
+ renderTypeParametersForNode(node, renderMode)
+ if (node.details(DocumentationNode.Kind.TypeParameter).any()) {
+ text(" ")
+ }
+
+ renderReceiver(node, renderMode, signatureMapper)
+
+ identifierOrDeprecated(node)
+ symbol(": ")
+ renderType(node.detail(DocumentationNode.Kind.Type), renderMode)
+ }
+
+ fun DocumentationNode.getPropertyKeyword() =
+ if (details(DocumentationNode.Kind.Modifier).any { it.name == "var" }) "var" else "val"
+
+ fun ContentBlock.identifierOrDeprecated(node: DocumentationNode) {
+ if (node.deprecation != null) {
+ val strike = ContentStrikethrough()
+ strike.identifier(node.name)
+ append(strike)
+ } else {
+ identifier(node.name)
+ }
+ }
+}
+
+fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name
diff --git a/core/src/main/kotlin/Languages/JavaLanguageService.kt b/core/src/main/kotlin/Languages/JavaLanguageService.kt
new file mode 100644
index 00000000..7e40beff
--- /dev/null
+++ b/core/src/main/kotlin/Languages/JavaLanguageService.kt
@@ -0,0 +1,162 @@
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.DocumentationNode.Kind
+import org.jetbrains.dokka.LanguageService.RenderMode
+
+/**
+ * Implements [LanguageService] and provides rendering of symbols in Java language
+ */
+public class JavaLanguageService : LanguageService {
+ override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode {
+ return ContentText(when (node.kind) {
+ Kind.Package -> renderPackage(node)
+ in Kind.classLike -> renderClass(node)
+
+ Kind.TypeParameter -> renderTypeParameter(node)
+ Kind.Type,
+ Kind.UpperBound -> renderType(node)
+
+ Kind.Constructor,
+ Kind.Function -> renderFunction(node)
+ Kind.Property -> renderProperty(node)
+ else -> "${node.kind}: ${node.name}"
+ })
+ }
+
+ override fun renderName(node: DocumentationNode): String {
+ return when (node.kind) {
+ Kind.Constructor -> node.owner!!.name
+ else -> node.name
+ }
+ }
+
+ override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? = null
+
+ private fun renderPackage(node: DocumentationNode): String {
+ return "package ${node.name}"
+ }
+
+ private fun renderModifier(node: DocumentationNode): String {
+ return when (node.name) {
+ "open" -> ""
+ "internal" -> ""
+ else -> node.name
+ }
+ }
+
+ public fun getArrayElementType(node: DocumentationNode): DocumentationNode? = when (node.name) {
+ "Array" -> node.details(Kind.Type).singleOrNull()?.let { et -> getArrayElementType(et) ?: et } ?: DocumentationNode("Object", node.content, DocumentationNode.Kind.ExternalClass)
+ "IntArray", "LongArray", "ShortArray", "ByteArray", "CharArray", "DoubleArray", "FloatArray", "BooleanArray" -> DocumentationNode(node.name.removeSuffix("Array").toLowerCase(), node.content, DocumentationNode.Kind.Type)
+ else -> null
+ }
+
+ public fun getArrayDimension(node: DocumentationNode): Int = when (node.name) {
+ "Array" -> 1 + (node.details(DocumentationNode.Kind.Type).singleOrNull()?.let { getArrayDimension(it) } ?: 0)
+ "IntArray", "LongArray", "ShortArray", "ByteArray", "CharArray", "DoubleArray", "FloatArray", "BooleanArray" -> 1
+ else -> 0
+ }
+
+ public fun renderType(node: DocumentationNode): String {
+ return when (node.name) {
+ "Unit" -> "void"
+ "Int" -> "int"
+ "Long" -> "long"
+ "Double" -> "double"
+ "Float" -> "float"
+ "Char" -> "char"
+ "Boolean" -> "bool"
+ // TODO: render arrays
+ else -> node.name
+ }
+ }
+
+ private fun renderTypeParameter(node: DocumentationNode): String {
+ val constraints = node.details(Kind.UpperBound)
+ return if (constraints.none())
+ node.name
+ else {
+ node.name + " extends " + constraints.map { renderType(node) }.joinToString()
+ }
+ }
+
+ private fun renderParameter(node: DocumentationNode): String {
+ return "${renderType(node.detail(Kind.Type))} ${node.name}"
+ }
+
+ private fun renderTypeParametersForNode(node: DocumentationNode): String {
+ return StringBuilder().apply {
+ val typeParameters = node.details(Kind.TypeParameter)
+ if (typeParameters.any()) {
+ append("<")
+ append(typeParameters.map { renderTypeParameter(it) }.joinToString())
+ append("> ")
+ }
+ }.toString()
+ }
+
+ private fun renderModifiersForNode(node: DocumentationNode): String {
+ val modifiers = node.details(Kind.Modifier).map { renderModifier(it) }.filter { it != "" }
+ if (modifiers.none())
+ return ""
+ return modifiers.joinToString(" ", postfix = " ")
+ }
+
+ private fun renderClass(node: DocumentationNode): String {
+ return StringBuilder().apply {
+ when (node.kind) {
+ Kind.Class -> append("class ")
+ Kind.Interface -> append("interface ")
+ Kind.Enum -> append("enum ")
+ Kind.EnumItem -> append("enum value ")
+ Kind.Object -> append("class ")
+ else -> throw IllegalArgumentException("Node $node is not a class-like object")
+ }
+
+ append(node.name)
+ append(renderTypeParametersForNode(node))
+ }.toString()
+ }
+
+ private fun renderFunction(node: DocumentationNode): String {
+ return StringBuilder().apply {
+ when (node.kind) {
+ Kind.Constructor -> append(node.owner?.name)
+ Kind.Function -> {
+ append(renderTypeParametersForNode(node))
+ append(renderType(node.detail(Kind.Type)))
+ append(" ")
+ append(node.name)
+ }
+ else -> throw IllegalArgumentException("Node $node is not a function-like object")
+ }
+
+ val receiver = node.details(Kind.Receiver).singleOrNull()
+ append("(")
+ if (receiver != null)
+ (listOf(receiver) + node.details(Kind.Parameter)).map { renderParameter(it) }.joinTo(this)
+ else
+ node.details(Kind.Parameter).map { renderParameter(it) }.joinTo(this)
+
+ append(")")
+ }.toString()
+ }
+
+ private fun renderProperty(node: DocumentationNode): String {
+ return StringBuilder().apply {
+ when (node.kind) {
+ Kind.Property -> append("val ")
+ else -> throw IllegalArgumentException("Node $node is not a property")
+ }
+ append(renderTypeParametersForNode(node))
+ val receiver = node.details(Kind.Receiver).singleOrNull()
+ if (receiver != null) {
+ append(renderType(receiver.detail(Kind.Type)))
+ append(".")
+ }
+
+ append(node.name)
+ append(": ")
+ append(renderType(node.detail(Kind.Type)))
+ }.toString()
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Languages/LanguageService.kt b/core/src/main/kotlin/Languages/LanguageService.kt
new file mode 100644
index 00000000..b0f4bbc9
--- /dev/null
+++ b/core/src/main/kotlin/Languages/LanguageService.kt
@@ -0,0 +1,41 @@
+package org.jetbrains.dokka
+
+/**
+ * Provides facility for rendering [DocumentationNode] as a language-dependent declaration
+ */
+interface LanguageService {
+ enum class RenderMode {
+ /** Brief signature (used in a list of all members of the class). */
+ SUMMARY,
+ /** Full signature (used in the page describing the member itself */
+ FULL
+ }
+
+ /**
+ * Renders a [node] as a class, function, property or other signature in a target language.
+ * @param node A [DocumentationNode] to render
+ * @return [ContentNode] which is a root for a rich content tree suitable for formatting with [FormatService]
+ */
+ fun render(node: DocumentationNode, renderMode: RenderMode = RenderMode.FULL): ContentNode
+
+ /**
+ * Tries to summarize the signatures of the specified documentation nodes in a compact representation.
+ * Returns the representation if successful, or null if the signatures could not be summarized.
+ */
+ fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode?
+
+ /**
+ * Renders [node] as a named representation in the target language
+ *
+ * For example:
+ * ${code org.jetbrains.dokka.example}
+ *
+ * $node: A [DocumentationNode] to render
+ * $returns: [String] which is a string representation of the node's name
+ */
+ fun renderName(node: DocumentationNode): String
+}
+
+fun example(service: LanguageService, node: DocumentationNode) {
+ println("Node name: ${service.renderName(node)}")
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Locations/FoldersLocationService.kt b/core/src/main/kotlin/Locations/FoldersLocationService.kt
new file mode 100644
index 00000000..89b34ed1
--- /dev/null
+++ b/core/src/main/kotlin/Locations/FoldersLocationService.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import java.io.File
+
+public fun FoldersLocationService(root: String): FoldersLocationService = FoldersLocationService(File(root), "")
+public class FoldersLocationService @Inject constructor(@Named("outputDir") val rootFile: File, val extension: String) : FileLocationService {
+ 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/LocationService.kt b/core/src/main/kotlin/Locations/LocationService.kt
new file mode 100644
index 00000000..80bc0236
--- /dev/null
+++ b/core/src/main/kotlin/Locations/LocationService.kt
@@ -0,0 +1,78 @@
+package org.jetbrains.dokka
+
+import java.io.File
+
+public 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
+ */
+public 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 "."
+ }
+ val ownerFolder = file.parentFile!!
+ val relativePath = ownerFolder.toPath().relativize(other.file.toPath()).toString()
+ 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.
+ */
+public 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
+}
+
+
+public 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
+}
+
+
+public 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
new file mode 100644
index 00000000..e313ac28
--- /dev/null
+++ b/core/src/main/kotlin/Locations/SingleFolderLocationService.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import java.io.File
+
+public fun SingleFolderLocationService(root: String): SingleFolderLocationService = SingleFolderLocationService(File(root), "")
+public class SingleFolderLocationService @Inject constructor(@Named("outputDir") val rootFile: File, val extension: String) : FileLocationService {
+ 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/Markdown/MarkdownProcessor.kt b/core/src/main/kotlin/Markdown/MarkdownProcessor.kt
new file mode 100644
index 00000000..99caddc4
--- /dev/null
+++ b/core/src/main/kotlin/Markdown/MarkdownProcessor.kt
@@ -0,0 +1,50 @@
+package org.jetbrains.dokka
+
+import org.intellij.markdown.IElementType
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.ast.ASTNode
+import org.intellij.markdown.ast.LeafASTNode
+import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
+import org.intellij.markdown.parser.MarkdownParser
+
+class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val markdown: String) {
+ val children: List<MarkdownNode> = node.children.map { MarkdownNode(it, this, markdown) }
+ val type: IElementType get() = node.type
+ val text: String get() = markdown.substring(node.startOffset, node.endOffset)
+ fun child(type: IElementType): MarkdownNode? = children.firstOrNull { it.type == type }
+
+ override fun toString(): String = StringBuilder().apply { presentTo(this) }.toString()
+}
+
+fun MarkdownNode.visit(action: (MarkdownNode, () -> Unit) -> Unit) {
+ action(this) {
+ for (child in children) {
+ child.visit(action)
+ }
+ }
+}
+
+public fun MarkdownNode.toTestString(): String {
+ val sb = StringBuilder()
+ var level = 0
+ visit { node, visitChildren ->
+ sb.append(" ".repeat(level * 2))
+ node.presentTo(sb)
+ level++
+ visitChildren()
+ level--
+ }
+ return sb.toString()
+}
+
+private fun MarkdownNode.presentTo(sb: StringBuilder) {
+ sb.append(type.toString())
+ sb.append(":" + text.replace("\n", "\u23CE"))
+ sb.appendln()
+}
+
+fun parseMarkdown(markdown: String): MarkdownNode {
+ if (markdown.isEmpty())
+ return MarkdownNode(LeafASTNode(MarkdownElementTypes.MARKDOWN_FILE, 0, 0), null, markdown)
+ return MarkdownNode(MarkdownParser(CommonMarkFlavourDescriptor()).buildMarkdownTreeFromString(markdown), null, markdown)
+}
diff --git a/core/src/main/kotlin/Model/Content.kt b/core/src/main/kotlin/Model/Content.kt
new file mode 100644
index 00000000..6556b09e
--- /dev/null
+++ b/core/src/main/kotlin/Model/Content.kt
@@ -0,0 +1,231 @@
+package org.jetbrains.dokka
+
+public interface ContentNode {
+ val textLength: Int
+}
+
+public object ContentEmpty : ContentNode {
+ override val textLength: Int get() = 0
+}
+
+public open class ContentBlock() : ContentNode {
+ val children = arrayListOf<ContentNode>()
+
+ fun append(node: ContentNode) {
+ children.add(node)
+ }
+
+ fun isEmpty() = children.isEmpty()
+
+ override fun equals(other: Any?): Boolean =
+ other is ContentBlock && javaClass == other.javaClass && children == other.children
+
+ override fun hashCode(): Int =
+ children.hashCode()
+
+ override val textLength: Int
+ get() = children.sumBy { it.textLength }
+}
+
+enum class IdentifierKind {
+ TypeName,
+ ParameterName,
+ AnnotationName,
+ SummarizedTypeName,
+ Other
+}
+
+public data class ContentText(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentKeyword(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentIdentifier(val text: String, val kind: IdentifierKind = IdentifierKind.Other) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentSymbol(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentEntity(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public object ContentNonBreakingSpace: ContentNode {
+ override val textLength: Int
+ get() = 1
+}
+
+public object ContentSoftLineBreak: ContentNode {
+ override val textLength: Int
+ get() = 0
+}
+
+public object ContentIndentedSoftLineBreak: ContentNode {
+ override val textLength: Int
+ get() = 0
+}
+
+public class ContentParagraph() : ContentBlock()
+public class ContentEmphasis() : ContentBlock()
+public class ContentStrong() : ContentBlock()
+public class ContentStrikethrough() : ContentBlock()
+public class ContentCode() : ContentBlock()
+public class ContentBlockCode(val language: String = "") : ContentBlock()
+
+public abstract class ContentNodeLink() : ContentBlock() {
+ abstract val node: DocumentationNode?
+}
+
+public class ContentNodeDirectLink(override val node: DocumentationNode): ContentNodeLink() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentNodeDirectLink && node.name == other.node.name
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + node.name.hashCode()
+}
+
+public class ContentNodeLazyLink(val linkText: String, val lazyNode: () -> DocumentationNode?): ContentNodeLink() {
+ override val node: DocumentationNode? get() = lazyNode()
+
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentNodeLazyLink && linkText == other.linkText
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + linkText.hashCode()
+}
+
+public class ContentExternalLink(val href : String) : ContentBlock() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentExternalLink && href == other.href
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + href.hashCode()
+}
+
+public class ContentUnorderedList() : ContentBlock()
+public class ContentOrderedList() : ContentBlock()
+public class ContentListItem() : ContentBlock()
+
+public class ContentHeading(val level: Int) : ContentBlock()
+
+public class ContentSection(public val tag: String, public val subjectName: String?) : ContentBlock() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentSection && tag == other.tag && subjectName == other.subjectName
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 * 31 + tag.hashCode() * 31 + (subjectName?.hashCode() ?: 0)
+}
+
+public object ContentTags {
+ val Description = "Description"
+ val SeeAlso = "See Also"
+}
+
+fun content(body: ContentBlock.() -> Unit): ContentBlock {
+ val block = ContentBlock()
+ block.body()
+ return block
+}
+
+fun ContentBlock.text(value: String) = append(ContentText(value))
+fun ContentBlock.keyword(value: String) = append(ContentKeyword(value))
+fun ContentBlock.symbol(value: String) = append(ContentSymbol(value))
+fun ContentBlock.identifier(value: String, kind: IdentifierKind = IdentifierKind.Other) = append(ContentIdentifier(value, kind))
+fun ContentBlock.nbsp() = append(ContentNonBreakingSpace)
+fun ContentBlock.softLineBreak() = append(ContentSoftLineBreak)
+fun ContentBlock.indentedSoftLineBreak() = append(ContentIndentedSoftLineBreak)
+
+fun ContentBlock.strong(body: ContentBlock.() -> Unit) {
+ val strong = ContentStrong()
+ strong.body()
+ append(strong)
+}
+
+fun ContentBlock.code(body: ContentBlock.() -> Unit) {
+ val code = ContentCode()
+ code.body()
+ append(code)
+}
+
+fun ContentBlock.link(to: DocumentationNode, body: ContentBlock.() -> Unit) {
+ val block = ContentNodeDirectLink(to)
+ block.body()
+ append(block)
+}
+
+public open class Content(): ContentBlock() {
+ public open val sections: List<ContentSection> get() = emptyList()
+ public open val summary: ContentNode get() = ContentEmpty
+ public open val description: ContentNode get() = ContentEmpty
+
+ fun findSectionByTag(tag: String): ContentSection? =
+ sections.firstOrNull { tag.equals(it.tag, ignoreCase = true) }
+
+ companion object {
+ val Empty = Content()
+
+ fun of(vararg child: ContentNode): Content {
+ val result = MutableContent()
+ child.forEach { result.append(it) }
+ return result
+ }
+ }
+}
+
+public open class MutableContent() : Content() {
+ private val sectionList = arrayListOf<ContentSection>()
+ public override val sections: List<ContentSection>
+ get() = sectionList
+
+ fun addSection(tag: String?, subjectName: String?): ContentSection {
+ val section = ContentSection(tag ?: "", subjectName)
+ sectionList.add(section)
+ return section
+ }
+
+ public override val summary: ContentNode get() = children.firstOrNull() ?: ContentEmpty
+
+ public override val description: ContentNode by lazy {
+ val descriptionNodes = children.drop(1)
+ if (descriptionNodes.isEmpty()) {
+ ContentEmpty
+ } else {
+ val result = ContentSection(ContentTags.Description, null)
+ result.children.addAll(descriptionNodes)
+ result
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Content)
+ return false
+ return sections == other.sections && children == other.children
+ }
+
+ override fun hashCode(): Int {
+ return sections.map { it.hashCode() }.sum()
+ }
+
+ override fun toString(): String {
+ if (sections.isEmpty())
+ return "<empty>"
+ return (listOf(summary, description) + sections).joinToString()
+ }
+}
+
+fun javadocSectionDisplayName(sectionName: String?): String? =
+ when(sectionName) {
+ "param" -> "Parameters"
+ "throws", "exception" -> "Exceptions"
+ else -> sectionName?.capitalize()
+ }
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
new file mode 100644
index 00000000..52881f65
--- /dev/null
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -0,0 +1,162 @@
+package org.jetbrains.dokka
+
+import java.util.*
+
+public open class DocumentationNode(val name: String,
+ content: Content,
+ val kind: DocumentationNode.Kind) {
+
+ private val references = LinkedHashSet<DocumentationReference>()
+
+ var content: Content = content
+ private set
+
+ public val summary: ContentNode get() = content.summary
+
+ public val owner: DocumentationNode?
+ get() = references(DocumentationReference.Kind.Owner).singleOrNull()?.to
+ public val details: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Detail).map { it.to }
+ public val members: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Member).map { it.to }
+ public val inheritedMembers: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.InheritedMember).map { it.to }
+ public val extensions: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Extension).map { it.to }
+ public val inheritors: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Inheritor).map { it.to }
+ public val overrides: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Override).map { it.to }
+ public val links: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Link).map { it.to }
+ public val hiddenLinks: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.HiddenLink).map { it.to }
+ public val annotations: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Annotation).map { it.to }
+ public val deprecation: DocumentationNode?
+ get() = references(DocumentationReference.Kind.Deprecation).singleOrNull()?.to
+
+ // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice
+ public fun addReferenceTo(to: DocumentationNode, kind: DocumentationReference.Kind) {
+ references.add(DocumentationReference(this, to, kind))
+ }
+
+ public fun addAllReferencesFrom(other: DocumentationNode) {
+ references.addAll(other.references)
+ }
+
+ public fun updateContent(body: MutableContent.() -> Unit) {
+ if (content !is MutableContent) {
+ content = MutableContent()
+ }
+ (content as MutableContent).body()
+ }
+
+ public fun details(kind: DocumentationNode.Kind): List<DocumentationNode> = details.filter { it.kind == kind }
+ public fun members(kind: DocumentationNode.Kind): List<DocumentationNode> = members.filter { it.kind == kind }
+ public fun inheritedMembers(kind: DocumentationNode.Kind): List<DocumentationNode> = inheritedMembers.filter { it.kind == kind }
+ public fun links(kind: DocumentationNode.Kind): List<DocumentationNode> = links.filter { it.kind == kind }
+
+ public fun detail(kind: DocumentationNode.Kind): DocumentationNode = details.filter { it.kind == kind }.single()
+ public fun member(kind: DocumentationNode.Kind): DocumentationNode = members.filter { it.kind == kind }.single()
+ public fun link(kind: DocumentationNode.Kind): DocumentationNode = links.filter { it.kind == kind }.single()
+
+ public fun references(kind: DocumentationReference.Kind): List<DocumentationReference> = references.filter { it.kind == kind }
+ public fun allReferences(): Set<DocumentationReference> = references
+
+ public override fun toString(): String {
+ return "$kind:$name"
+ }
+
+ public enum class Kind {
+ Unknown,
+
+ Package,
+ Class,
+ Interface,
+ Enum,
+ AnnotationClass,
+ EnumItem,
+ Object,
+
+ Constructor,
+ Function,
+ Property,
+ Field,
+
+ CompanionObjectProperty,
+ CompanionObjectFunction,
+
+ Parameter,
+ Receiver,
+ TypeParameter,
+ Type,
+ Supertype,
+ UpperBound,
+ LowerBound,
+ Exception,
+
+ Modifier,
+ NullabilityModifier,
+
+ Module,
+
+ ExternalClass,
+ Annotation,
+
+ Value,
+
+ SourceUrl,
+ SourcePosition,
+
+ /**
+ * A note which is rendered once on a page documenting a group of overloaded functions.
+ * Needs to be generated equally on all overloads.
+ */
+ OverloadGroupNote;
+
+ companion object {
+ val classLike = setOf(Class, Interface, Enum, AnnotationClass, Object)
+ }
+ }
+}
+
+public class DocumentationModule(name: String, content: Content = Content.Empty)
+ : DocumentationNode(name, content, DocumentationNode.Kind.Module) {
+}
+
+val DocumentationNode.path: List<DocumentationNode>
+ get() {
+ val parent = owner ?: return listOf(this)
+ return parent.path + this
+ }
+
+fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>): DocumentationNode {
+ val existingNode = members(DocumentationNode.Kind.Package).firstOrNull { it.name == packageName }
+ if (existingNode != null) {
+ return existingNode
+ }
+ val newNode = DocumentationNode(packageName,
+ packageContent.getOrElse(packageName) { Content.Empty },
+ DocumentationNode.Kind.Package)
+ append(newNode, DocumentationReference.Kind.Member)
+ return newNode
+}
+
+fun DocumentationNode.append(child: DocumentationNode, kind: DocumentationReference.Kind) {
+ addReferenceTo(child, kind)
+ when (kind) {
+ DocumentationReference.Kind.Detail -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
+ DocumentationReference.Kind.Member -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
+ DocumentationReference.Kind.Owner -> child.addReferenceTo(this, DocumentationReference.Kind.Member)
+ else -> { /* Do not add any links back for other types */ }
+ }
+}
+
+fun DocumentationNode.appendTextNode(text: String,
+ kind: DocumentationNode.Kind,
+ refKind: DocumentationReference.Kind = DocumentationReference.Kind.Detail) {
+ append(DocumentationNode(text, Content.Empty, kind), refKind)
+}
+
+fun DocumentationNode.qualifiedName() = path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".")
diff --git a/core/src/main/kotlin/Model/DocumentationReference.kt b/core/src/main/kotlin/Model/DocumentationReference.kt
new file mode 100644
index 00000000..898c92d7
--- /dev/null
+++ b/core/src/main/kotlin/Model/DocumentationReference.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Singleton
+
+public data class DocumentationReference(val from: DocumentationNode, val to: DocumentationNode, val kind: DocumentationReference.Kind) {
+ public enum class Kind {
+ Owner,
+ Member,
+ InheritedMember,
+ Detail,
+ Link,
+ HiddenLink,
+ Extension,
+ Inheritor,
+ Superclass,
+ Override,
+ Annotation,
+ Deprecation,
+ TopLevelPage
+ }
+}
+
+class PendingDocumentationReference(val lazyNodeFrom: () -> DocumentationNode?,
+ val lazyNodeTo: () -> DocumentationNode?,
+ val kind: DocumentationReference.Kind) {
+ fun resolve() {
+ val fromNode = lazyNodeFrom()
+ val toNode = lazyNodeTo()
+ if (fromNode != null && toNode != null) {
+ fromNode.addReferenceTo(toNode, kind)
+ }
+ }
+}
+
+@Singleton
+class NodeReferenceGraph() {
+ private val nodeMap = hashMapOf<String, DocumentationNode>()
+ val references = arrayListOf<PendingDocumentationReference>()
+
+ fun register(signature: String, node: DocumentationNode) {
+ nodeMap.put(signature, node)
+ }
+
+ fun link(fromNode: DocumentationNode, toSignature: String, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> fromNode}, { -> nodeMap[toSignature]}, kind))
+ }
+
+ fun link(fromSignature: String, toNode: DocumentationNode, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> toNode}, kind))
+ }
+
+ fun link(fromSignature: String, toSignature: String, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> nodeMap[toSignature]}, kind))
+ }
+
+ fun lookup(signature: String): DocumentationNode? = nodeMap[signature]
+
+ fun resolveReferences() {
+ references.forEach { it.resolve() }
+ }
+}
diff --git a/core/src/main/kotlin/Model/PackageDocs.kt b/core/src/main/kotlin/Model/PackageDocs.kt
new file mode 100644
index 00000000..044c73d8
--- /dev/null
+++ b/core/src/main/kotlin/Model/PackageDocs.kt
@@ -0,0 +1,60 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.Singleton
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
+import java.io.File
+
+@Singleton
+public class PackageDocs
+ @Inject constructor(val linkResolver: DeclarationLinkResolver?,
+ val logger: DokkaLogger)
+{
+ public val moduleContent: MutableContent = MutableContent()
+ private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
+ public val packageContent: Map<String, Content>
+ get() = _packageContent
+
+ fun parse(fileName: String, linkResolveContext: LazyPackageDescriptor?) {
+ val file = File(fileName)
+ if (file.exists()) {
+ val text = file.readText()
+ val tree = parseMarkdown(text)
+ var targetContent: MutableContent = moduleContent
+ tree.children.forEach {
+ if (it.type == MarkdownElementTypes.ATX_1) {
+ val headingText = it.child(MarkdownTokenTypes.ATX_CONTENT)?.text
+ if (headingText != null) {
+ targetContent = findTargetContent(headingText.trimStart())
+ }
+ } else {
+ buildContentTo(it, targetContent, { resolveContentLink(it, linkResolveContext) })
+ }
+ }
+ } else {
+ logger.warn("Include file $file was not found.")
+ }
+ }
+
+ private fun findTargetContent(heading: String): MutableContent {
+ if (heading.startsWith("Module") || heading.startsWith("module")) {
+ return moduleContent
+ }
+ if (heading.startsWith("Package") || heading.startsWith("package")) {
+ return findOrCreatePackageContent(heading.substring("package".length).trim())
+ }
+ return findOrCreatePackageContent(heading)
+ }
+
+ private fun findOrCreatePackageContent(packageName: String) =
+ _packageContent.getOrPut(packageName) { -> MutableContent() }
+
+ private fun resolveContentLink(href: String, linkResolveContext: LazyPackageDescriptor?): ContentBlock {
+ if (linkResolveContext != null && linkResolver != null) {
+ return linkResolver.resolveContentLink(linkResolveContext, href)
+ }
+ return ContentExternalLink("#")
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/SourceLinks.kt b/core/src/main/kotlin/Model/SourceLinks.kt
new file mode 100644
index 00000000..956bfe4b
--- /dev/null
+++ b/core/src/main/kotlin/Model/SourceLinks.kt
@@ -0,0 +1,56 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiElement
+import java.io.File
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiNameIdentifierOwner
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+
+class SourceLinkDefinition(val path: String, val url: String, val lineSuffix: String?)
+
+fun DocumentationNode.appendSourceLink(psi: PsiElement?, sourceLinks: List<SourceLinkDefinition>) {
+ val path = psi?.containingFile?.virtualFile?.path ?: return
+
+ val target = if (psi is PsiNameIdentifierOwner) psi.nameIdentifier else psi
+ val absPath = File(path).absolutePath
+ val linkDef = sourceLinks.firstOrNull { absPath.startsWith(it.path) }
+ if (linkDef != null) {
+ var url = linkDef.url + path.substring(linkDef.path.length)
+ if (linkDef.lineSuffix != null) {
+ val line = target?.lineNumber()
+ if (line != null) {
+ url += linkDef.lineSuffix + line.toString()
+ }
+ }
+ append(DocumentationNode(url, Content.Empty, DocumentationNode.Kind.SourceUrl),
+ DocumentationReference.Kind.Detail);
+ }
+
+ if (target != null) {
+ append(DocumentationNode(target.sourcePosition(), Content.Empty, DocumentationNode.Kind.SourcePosition), DocumentationReference.Kind.Detail)
+ }
+}
+
+private fun PsiElement.sourcePosition(): String {
+ val path = containingFile.virtualFile.path
+ val lineNumber = lineNumber()
+ val columnNumber = columnNumber()
+
+ return when {
+ lineNumber == null -> path
+ columnNumber == null -> "$path:$lineNumber"
+ else -> "$path:$lineNumber:$columnNumber"
+ }
+}
+
+fun PsiElement.lineNumber(): Int? {
+ val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile)
+ // IJ uses 0-based line-numbers; external source browsers use 1-based
+ return doc?.getLineNumber(textRange.startOffset)?.plus(1)
+}
+
+fun PsiElement.columnNumber(): Int? {
+ val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile) ?: return null
+ val lineNumber = doc.getLineNumber(textRange.startOffset)
+ return startOffset - doc.getLineStartOffset(lineNumber)
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Utilities/DokkaModule.kt b/core/src/main/kotlin/Utilities/DokkaModule.kt
new file mode 100644
index 00000000..1eb82313
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/DokkaModule.kt
@@ -0,0 +1,73 @@
+package org.jetbrains.dokka.Utilities
+
+import com.google.inject.Binder
+import com.google.inject.Module
+import com.google.inject.Provider
+import com.google.inject.name.Names
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import java.io.File
+
+class DokkaModule(val environment: AnalysisEnvironment,
+ val options: DocumentationOptions,
+ val logger: DokkaLogger) : Module {
+ override fun configure(binder: Binder) {
+ 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.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.registerCategory<LanguageService>("language")
+ 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<PackageDocumentationBuilder>().to(descriptor.packageDocumentationBuilderClass.java)
+ binder.bind<JavaDocumentationBuilder>().to(descriptor.javaDocumentationBuilderClass.java)
+
+ binder.bind<Generator>().to(descriptor.generatorServiceClass.java)
+
+ val coreEnvironment = environment.createCoreEnvironment()
+ binder.bind<KotlinCoreEnvironment>().toInstance(coreEnvironment)
+
+ val dokkaResolutionFacade = environment.createResolutionFacade(coreEnvironment)
+ binder.bind<DokkaResolutionFacade>().toInstance(dokkaResolutionFacade)
+
+ binder.bind<DocumentationOptions>().toInstance(options)
+ binder.bind<DokkaLogger>().toInstance(logger)
+ }
+}
+
+private inline fun <reified T: Any> Binder.registerCategory(category: String) {
+ ServiceLocator.allServices(category).forEach {
+ @Suppress("UNCHECKED_CAST")
+ bind(T::class.java).annotatedWith(Names.named(it.name)).to(T::class.java.classLoader.loadClass(it.className) as Class<T>)
+ }
+}
+
+private inline fun <reified Base : Any, reified T : Base> Binder.bindNameAnnotated(name: String) {
+ bind(Base::class.java).annotatedWith(Names.named(name)).to(T::class.java)
+}
+
+
+inline fun <reified T: Any> Binder.bind() = bind(T::class.java)
diff --git a/core/src/main/kotlin/Utilities/Html.kt b/core/src/main/kotlin/Utilities/Html.kt
new file mode 100644
index 00000000..ce3a1982
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/Html.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka
+
+
+/**
+ * Replaces symbols reserved in HTML with their respective entities.
+ * Replaces & with &amp;, < with &lt; and > with &gt;
+ */
+public fun String.htmlEscape(): String = replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
diff --git a/core/src/main/kotlin/Utilities/Path.kt b/core/src/main/kotlin/Utilities/Path.kt
new file mode 100644
index 00000000..05838499
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/Path.kt
@@ -0,0 +1,5 @@
+package org.jetbrains.dokka
+
+import java.io.File
+
+fun File.appendExtension(extension: String) = if (extension.isEmpty()) this else File(path + "." + extension)
diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt
new file mode 100644
index 00000000..7a5aff79
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt
@@ -0,0 +1,78 @@
+package org.jetbrains.dokka.Utilities
+
+import java.io.File
+import java.util.*
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+
+data class ServiceDescriptor(val name: String, val category: String, val description: String?, val className: String)
+
+class ServiceLookupException(message: String) : Exception(message)
+
+public object ServiceLocator {
+ public fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
+ val descriptor = lookupDescriptor(category, implementationName)
+ 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")
+
+ 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}")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return implementationRawType as T
+ }
+
+ private fun lookupDescriptor(category: String, implementationName: String): ServiceDescriptor {
+ val properties = javaClass.classLoader.getResourceAsStream("dokka/$category/$implementationName.properties")?.use { stream ->
+ Properties().let { properties ->
+ properties.load(stream)
+ properties
+ }
+ } ?: throw ServiceLookupException("No implementation with name $implementationName found in category $category")
+
+ val className = properties["class"]?.toString() ?: throw ServiceLookupException("Implementation $implementationName has no class configured")
+
+ return ServiceDescriptor(implementationName, category, properties["description"]?.toString(), className)
+ }
+
+ fun allServices(category: String): List<ServiceDescriptor> {
+ val entries = this.javaClass.classLoader.getResources("dokka/$category")?.toList() ?: emptyList()
+
+ return entries.flatMap {
+ when (it.protocol) {
+ "file" -> File(it.file).listFiles()?.filter { it.extension == "properties" }?.map { lookupDescriptor(category, it.nameWithoutExtension) } ?: emptyList()
+ "jar" -> {
+ val file = JarFile(it.file.removePrefix("file:").substringBefore("!"))
+ try {
+ val jarPath = it.file.substringAfterLast("!").removePrefix("/")
+ file.entries()
+ .asSequence()
+ .filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" }
+ .map { entry ->
+ lookupDescriptor(category, entry.fileName.substringBeforeLast("."))
+ }.toList()
+ } finally {
+ file.close()
+ }
+ }
+ else -> emptyList<ServiceDescriptor>()
+ }
+ }
+ }
+}
+
+public inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
+
+private val ZipEntry.fileName: String
+ get() = name.substringAfterLast("/", name)
+
+private val ZipEntry.path: String
+ get() = name.substringBeforeLast("/", "").removePrefix("/")
+
+private val ZipEntry.extension: String?
+ get() = fileName.let { fn -> if ("." in fn) fn.substringAfterLast(".") else null }
diff --git a/core/src/main/kotlin/ant/dokka.kt b/core/src/main/kotlin/ant/dokka.kt
new file mode 100644
index 00000000..d78980f8
--- /dev/null
+++ b/core/src/main/kotlin/ant/dokka.kt
@@ -0,0 +1,108 @@
+package org.jetbrains.dokka.ant
+
+import org.apache.tools.ant.Task
+import org.apache.tools.ant.types.Path
+import org.apache.tools.ant.types.Reference
+import org.apache.tools.ant.BuildException
+import org.apache.tools.ant.Project
+import org.jetbrains.dokka.DokkaLogger
+import org.jetbrains.dokka.DokkaGenerator
+import org.jetbrains.dokka.SourceLinkDefinition
+import java.io.File
+
+class AntLogger(val task: Task): DokkaLogger {
+ override fun info(message: String) = task.log(message, Project.MSG_INFO)
+ override fun warn(message: String) = task.log(message, Project.MSG_WARN)
+ override fun error(message: String) = task.log(message, Project.MSG_ERR)
+}
+
+class AntSourceLinkDefinition(var path: String? = null, var url: String? = null, var lineSuffix: String? = null)
+
+class DokkaAntTask(): Task() {
+ public var moduleName: String? = null
+ public var outputDir: String? = null
+ public var outputFormat: String = "html"
+
+ public var skipDeprecated: Boolean = false
+
+ public val compileClasspath: Path = Path(getProject())
+ public val sourcePath: Path = Path(getProject())
+ public val samplesPath: Path = Path(getProject())
+ public val includesPath: Path = Path(getProject())
+
+ public val antSourceLinks: MutableList<AntSourceLinkDefinition> = arrayListOf()
+
+ public fun setClasspath(classpath: Path) {
+ compileClasspath.append(classpath)
+ }
+
+ public fun setClasspathRef(ref: Reference) {
+ compileClasspath.createPath().refid = ref
+ }
+
+ public fun setSrc(src: Path) {
+ sourcePath.append(src)
+ }
+
+ public fun setSrcRef(ref: Reference) {
+ sourcePath.createPath().refid = ref
+ }
+
+ public fun setSamples(samples: Path) {
+ samplesPath.append(samples)
+ }
+
+ public fun setSamplesRef(ref: Reference) {
+ samplesPath.createPath().refid = ref
+ }
+
+ public fun setInclude(include: Path) {
+ includesPath.append(include)
+ }
+
+ public fun createSourceLink(): AntSourceLinkDefinition {
+ val def = AntSourceLinkDefinition()
+ antSourceLinks.add(def)
+ return def
+ }
+
+ override fun execute() {
+ if (sourcePath.list().size == 0) {
+ throw BuildException("At least one source path needs to be specified")
+ }
+ if (moduleName == null) {
+ throw BuildException("Module name needs to be specified")
+ }
+ if (outputDir == null) {
+ throw BuildException("Output directory needs to be specified")
+ }
+ val sourceLinks = antSourceLinks.map {
+ val path = it.path
+ if (path == null) {
+ throw BuildException("Path attribute of a <sourceLink> element is required")
+ }
+ val url = it.url
+ if (url == null) {
+ throw BuildException("Path attribute of a <sourceLink> element is required")
+ }
+ SourceLinkDefinition(File(path).canonicalFile.absolutePath, url, it.lineSuffix)
+ }
+
+ val url = DokkaAntTask::class.java.getResource("/org/jetbrains/dokka/ant/DokkaAntTask.class")
+ val jarRoot = url.path.substringBefore("!/").removePrefix("file:")
+
+ val generator = DokkaGenerator(
+ AntLogger(this),
+ listOf(jarRoot) + compileClasspath.list().toList(),
+ sourcePath.list().toList(),
+ samplesPath.list().toList(),
+ includesPath.list().toList(),
+ moduleName!!,
+ outputDir!!,
+ outputFormat,
+ sourceLinks,
+ skipDeprecated
+ )
+ generator.generate()
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/javadoc/docbase.kt b/core/src/main/kotlin/javadoc/docbase.kt
new file mode 100644
index 00000000..a0caca94
--- /dev/null
+++ b/core/src/main/kotlin/javadoc/docbase.kt
@@ -0,0 +1,501 @@
+package org.jetbrains.dokka.javadoc
+
+import com.sun.javadoc.*
+import org.jetbrains.dokka.*
+import java.lang.reflect.Modifier
+import java.util.*
+import kotlin.reflect.KClass
+
+private interface HasModule {
+ val module: ModuleNodeAdapter
+}
+
+private interface HasDocumentationNode {
+ val node: DocumentationNode
+}
+
+open class DocumentationNodeBareAdapter(override val node: DocumentationNode) : Doc, HasDocumentationNode {
+ private var rawCommentText_: String? = null
+
+ override fun name(): String = node.name
+ override fun position(): SourcePosition? = SourcePositionAdapter(node)
+
+ override fun inlineTags(): Array<out Tag>? = emptyArray()
+ override fun firstSentenceTags(): Array<out Tag>? = emptyArray()
+ override fun tags(): Array<out Tag> = emptyArray()
+ override fun tags(tagname: String?): Array<out Tag>? = tags().filter { it.kind() == tagname || it.kind() == "@$tagname" }.toTypedArray()
+ override fun seeTags(): Array<out SeeTag>? = tags().filterIsInstance<SeeTag>().toTypedArray()
+ override fun commentText(): String = ""
+
+ override fun setRawCommentText(rawDocumentation: String?) {
+ rawCommentText_ = rawDocumentation ?: ""
+ }
+
+ override fun getRawCommentText(): String = rawCommentText_ ?: ""
+
+ override fun isError(): Boolean = false
+ override fun isException(): Boolean = node.kind == DocumentationNode.Kind.Exception
+ override fun isEnumConstant(): Boolean = node.kind == DocumentationNode.Kind.EnumItem
+ override fun isEnum(): Boolean = node.kind == DocumentationNode.Kind.Enum
+ override fun isMethod(): Boolean = node.kind == DocumentationNode.Kind.Function
+ override fun isInterface(): Boolean = node.kind == DocumentationNode.Kind.Interface
+ override fun isField(): Boolean = node.kind == DocumentationNode.Kind.Field
+ override fun isClass(): Boolean = node.kind == DocumentationNode.Kind.Class
+ override fun isAnnotationType(): Boolean = node.kind == DocumentationNode.Kind.AnnotationClass
+ override fun isConstructor(): Boolean = node.kind == DocumentationNode.Kind.Constructor
+ override fun isOrdinaryClass(): Boolean = node.kind == DocumentationNode.Kind.Class
+ override fun isAnnotationTypeElement(): Boolean = node.kind == DocumentationNode.Kind.Annotation
+
+ override fun compareTo(other: Any?): Int = when (other) {
+ !is DocumentationNodeAdapter -> 1
+ else -> node.name.compareTo(other.node.name)
+ }
+
+ override fun equals(other: Any?): Boolean = node.qualifiedName() == (other as? DocumentationNodeAdapter)?.node?.qualifiedName()
+ override fun hashCode(): Int = node.name.hashCode()
+
+ override fun isIncluded(): Boolean = node.kind != DocumentationNode.Kind.ExternalClass
+}
+
+
+// TODO think of source position instead of null
+// TODO tags
+open class DocumentationNodeAdapter(override val module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeBareAdapter(node), HasModule {
+ override fun inlineTags(): Array<out Tag> = buildInlineTags(module, this, node.content).toTypedArray()
+ override fun firstSentenceTags(): Array<out Tag> = buildInlineTags(module, this, node.summary).toTypedArray()
+
+ override fun tags(): Array<out Tag> {
+ val result = ArrayList<Tag>(buildInlineTags(module, this, node.content))
+ node.content.sections.flatMapTo(result) {
+ when (it.tag) {
+ ContentTags.SeeAlso -> buildInlineTags(module, this, it)
+ else -> emptyList<Tag>()
+ }
+ }
+
+ node.deprecation?.let {
+ val content = it.content.asText()
+ if (content != null) {
+ result.add(TagImpl(this, "deprecated", content))
+ }
+ }
+
+ return result.toTypedArray()
+ }
+}
+
+// should be extension property but can't because of KT-8745
+private fun <T> nodeAnnotations(self: T): List<AnnotationDescAdapter> where T : HasModule, T : HasDocumentationNode
+ = self.node.annotations.map { AnnotationDescAdapter(self.module, it) }
+
+private fun DocumentationNode.hasAnnotation(klass: KClass<*>) = klass.qualifiedName in annotations.map { it.qualifiedName() }
+private fun DocumentationNode.hasModifier(name: String) = details(DocumentationNode.Kind.Modifier).any { it.name == name }
+
+
+class PackageAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), PackageDoc {
+ private val allClasses = listOf(node).collectAllTypesRecursively()
+
+ override fun findClass(className: String?): ClassDoc? =
+ allClasses.get(className)?.let { ClassDocumentationNodeAdapter(module, it) }
+
+ override fun annotationTypes(): Array<out AnnotationTypeDoc> = emptyArray()
+ override fun annotations(): Array<out AnnotationDesc> = node.members(DocumentationNode.Kind.AnnotationClass).map { AnnotationDescAdapter(module, it) }.toTypedArray()
+ override fun exceptions(): Array<out ClassDoc> = node.members(DocumentationNode.Kind.Exception).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun ordinaryClasses(): Array<out ClassDoc> = node.members(DocumentationNode.Kind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun interfaces(): Array<out ClassDoc> = node.members(DocumentationNode.Kind.Interface).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun errors(): Array<out ClassDoc> = emptyArray()
+ override fun enums(): Array<out ClassDoc> = node.members(DocumentationNode.Kind.Enum).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun allClasses(filter: Boolean): Array<out ClassDoc> = allClasses.values.map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun allClasses(): Array<out ClassDoc> = allClasses(true)
+
+ override fun isIncluded(): Boolean = node.name in module.allPackages
+}
+
+class AnnotationTypeDocAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ClassDocumentationNodeAdapter(module, node), AnnotationTypeDoc {
+ override fun elements(): Array<out AnnotationTypeElementDoc>? = emptyArray() // TODO
+}
+
+class AnnotationDescAdapter(val module: ModuleNodeAdapter, val node: DocumentationNode) : AnnotationDesc {
+ override fun annotationType(): AnnotationTypeDoc? = AnnotationTypeDocAdapter(module, node) // TODO ?????
+ override fun isSynthesized(): Boolean = false
+ override fun elementValues(): Array<out AnnotationDesc.ElementValuePair>? = emptyArray() // TODO
+}
+
+class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc {
+ override fun isPublic(): Boolean = true
+ override fun isPackagePrivate(): Boolean = false
+ override fun isStatic(): Boolean = node.hasModifier("static")
+ override fun modifierSpecifier(): Int = Modifier.PUBLIC + if (isStatic) Modifier.STATIC else 0
+ override fun qualifiedName(): String? = if (node.kind == DocumentationNode.Kind.Type) node.qualifiedNameFromType() else node.qualifiedName()
+ override fun annotations(): Array<out AnnotationDesc>? = nodeAnnotations(this).toTypedArray()
+ override fun modifiers(): String? = "public ${if (isStatic) "static" else ""}".trim()
+ override fun isProtected(): Boolean = false
+
+ override fun isFinal(): Boolean = node.hasModifier("final")
+
+ override fun containingPackage(): PackageDoc? {
+ if (node.kind == DocumentationNode.Kind.Type) {
+ return null
+ }
+
+ var owner: DocumentationNode? = node
+ while (owner != null) {
+ if (owner.kind == DocumentationNode.Kind.Package) {
+ return PackageAdapter(module, owner)
+ }
+ owner = owner.owner
+ }
+
+ return null
+ }
+
+ override fun containingClass(): ClassDoc? {
+ if (node.kind == DocumentationNode.Kind.Type) {
+ return null
+ }
+
+ var owner = node.owner
+ while (owner != null) {
+ when (owner.kind) {
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.Interface,
+ DocumentationNode.Kind.Enum -> return ClassDocumentationNodeAdapter(module, owner)
+ else -> owner = owner.owner
+ }
+ }
+
+ return null
+ }
+
+ override fun isPrivate(): Boolean = false
+ override fun isIncluded(): Boolean = containingPackage()?.isIncluded ?: false && containingClass()?.let { it.isIncluded } ?: true
+}
+
+open class TypeAdapter(override val module: ModuleNodeAdapter, override val node: DocumentationNode) : Type, HasDocumentationNode, HasModule {
+ private val javaLanguageService = JavaLanguageService()
+
+ override fun qualifiedTypeName(): String = javaLanguageService.getArrayElementType(node)?.qualifiedNameFromType() ?: node.qualifiedNameFromType()
+ override fun typeName(): String = javaLanguageService.getArrayElementType(node)?.name ?: node.name
+ override fun simpleTypeName(): String = typeName() // TODO difference typeName() vs simpleTypeName()
+
+ override fun dimension(): String = Collections.nCopies(javaLanguageService.getArrayDimension(node), "[]").joinToString("")
+ override fun isPrimitive(): Boolean = simpleTypeName() in setOf("int", "long", "short", "byte", "char", "double", "float", "boolean", "void")
+
+ override fun asClassDoc(): ClassDoc? = if (isPrimitive) null else
+ elementType?.asClassDoc() ?:
+ when (node.kind) {
+ in DocumentationNode.Kind.classLike,
+ DocumentationNode.Kind.ExternalClass,
+ DocumentationNode.Kind.Exception -> module.classNamed(qualifiedTypeName()) ?: ClassDocumentationNodeAdapter(module, node)
+
+ else -> when {
+ node.links.isNotEmpty() -> TypeAdapter(module, node.links.first()).asClassDoc()
+ else -> ClassDocumentationNodeAdapter(module, node) // TODO ?
+ }
+ }
+
+ override fun asTypeVariable(): TypeVariable? = if (node.kind == DocumentationNode.Kind.TypeParameter) TypeVariableAdapter(module, node) else null
+ override fun asParameterizedType(): ParameterizedType? =
+ if (node.details(DocumentationNode.Kind.Type).isNotEmpty()) ParameterizedTypeAdapter(module, node)
+ else null // TODO it should ignore dimensions
+
+ override fun asAnnotationTypeDoc(): AnnotationTypeDoc? = if (node.kind == DocumentationNode.Kind.AnnotationClass) AnnotationTypeDocAdapter(module, node) else null
+ override fun asAnnotatedType(): AnnotatedType? = if (node.annotations.isNotEmpty()) AnnotatedTypeAdapter(module, node) else null
+ override fun getElementType(): Type? = javaLanguageService.getArrayElementType(node)?.let { et -> TypeAdapter(module, et) }
+ override fun asWildcardType(): WildcardType? = null
+
+ override fun toString(): String = qualifiedTypeName() + dimension()
+ override fun hashCode(): Int = node.name.hashCode()
+ override fun equals(other: Any?): Boolean = other is TypeAdapter && toString() == other.toString()
+}
+
+class AnnotatedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), AnnotatedType {
+ override fun underlyingType(): Type? = this
+ override fun annotations(): Array<out AnnotationDesc> = nodeAnnotations(this).toTypedArray()
+}
+
+class WildcardTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), WildcardType {
+ override fun extendsBounds(): Array<out Type> = node.details(DocumentationNode.Kind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray()
+ override fun superBounds(): Array<out Type> = node.details(DocumentationNode.Kind.LowerBound).map { TypeAdapter(module, it) }.toTypedArray()
+}
+
+class TypeVariableAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), TypeVariable {
+ override fun owner(): ProgramElementDoc = node.owner!!.let<DocumentationNode, ProgramElementDoc> { owner ->
+ when (owner.kind) {
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.Constructor -> ExecutableMemberAdapter(module, owner)
+
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.Interface,
+ DocumentationNode.Kind.Enum -> ClassDocumentationNodeAdapter(module, owner)
+
+ else -> ProgramElementAdapter(module, node.owner!!)
+ }
+ }
+
+ override fun bounds(): Array<out Type>? = node.details(DocumentationNode.Kind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray()
+ override fun annotations(): Array<out AnnotationDesc>? = node.members(DocumentationNode.Kind.Annotation).map { AnnotationDescAdapter(module, it) }.toTypedArray()
+
+ override fun qualifiedTypeName(): String = node.name
+ override fun simpleTypeName(): String = node.name
+ override fun typeName(): String = node.name
+
+ override fun hashCode(): Int = node.name.hashCode()
+ override fun equals(other: Any?): Boolean = other is Type && other.typeName() == typeName() && other.asTypeVariable()?.owner() == owner()
+
+ override fun asTypeVariable(): TypeVariableAdapter = this
+}
+
+class ParameterizedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), ParameterizedType {
+ override fun typeArguments(): Array<out Type> = node.details(DocumentationNode.Kind.Type).map { TypeVariableAdapter(module, it) }.toTypedArray()
+ override fun superclassType(): Type? =
+ node.lookupSuperClasses(module)
+ .firstOrNull { it.kind == DocumentationNode.Kind.Class || it.kind == DocumentationNode.Kind.ExternalClass }
+ ?.let { ClassDocumentationNodeAdapter(module, it) }
+
+ override fun interfaceTypes(): Array<out Type> =
+ node.lookupSuperClasses(module)
+ .filter { it.kind == DocumentationNode.Kind.Interface }
+ .map { ClassDocumentationNodeAdapter(module, it) }
+ .toTypedArray()
+
+ override fun containingType(): Type? = when (node.owner?.kind) {
+ DocumentationNode.Kind.Package -> null
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.Interface,
+ DocumentationNode.Kind.Object,
+ DocumentationNode.Kind.Enum -> ClassDocumentationNodeAdapter(module, node.owner!!)
+
+ else -> null
+ }
+}
+
+class ParameterAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), Parameter {
+ override fun typeName(): String? = JavaLanguageService().renderType(node.detail(DocumentationNode.Kind.Type))
+ override fun type(): Type? = TypeAdapter(module, node.detail(DocumentationNode.Kind.Type))
+ override fun annotations(): Array<out AnnotationDesc> = nodeAnnotations(this).toTypedArray()
+}
+
+class ReceiverParameterAdapter(module: ModuleNodeAdapter, val receiverType: DocumentationNode, val parent: ExecutableMemberAdapter) : DocumentationNodeAdapter(module, receiverType), Parameter {
+ override fun typeName(): String? = receiverType.name
+ override fun type(): Type? = TypeAdapter(module, receiverType)
+ override fun annotations(): Array<out AnnotationDesc> = nodeAnnotations(this).toTypedArray()
+ override fun name(): String = tryName("receiver")
+
+ private tailrec fun tryName(name: String): String = when (name) {
+ in parent.parameters().drop(1).map { it.name() } -> tryName("$$name")
+ else -> name
+ }
+}
+
+fun classOf(fqName: String, kind: DocumentationNode.Kind = DocumentationNode.Kind.Class) = DocumentationNode(fqName.substringAfterLast(".", fqName), Content.Empty, kind).let { node ->
+ val pkg = fqName.substringBeforeLast(".", "")
+ if (pkg.isNotEmpty()) {
+ node.append(DocumentationNode(pkg, Content.Empty, DocumentationNode.Kind.Package), DocumentationReference.Kind.Owner)
+ }
+
+ node
+}
+
+open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc by ProgramElementAdapter(module, node), ExecutableMemberDoc {
+
+ override fun isSynthetic(): Boolean = false
+ override fun isNative(): Boolean = node.annotations.any { it.name == "native" }
+
+ override fun thrownExceptions(): Array<out ClassDoc> = emptyArray() // TODO
+ override fun throwsTags(): Array<out ThrowsTag> =
+ node.content.sections
+ .filter { it.tag == "Exceptions" }
+ .map { it.subjectName }
+ .filterNotNull()
+ .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it, DocumentationNode.Kind.Exception))) }
+ .toTypedArray()
+
+ override fun isVarArgs(): Boolean = node.details(DocumentationNode.Kind.Parameter).any { false } // TODO
+
+ override fun isSynchronized(): Boolean = node.annotations.any { it.name == "synchronized" }
+
+ override fun paramTags(): Array<out ParamTag> = node.details(DocumentationNode.Kind.Parameter)
+ .filter { it.content.summary !is ContentEmpty || it.content.description !is ContentEmpty || it.content.sections.isNotEmpty() }
+ .map { ParamTagAdapter(module, this, it.name, false, it.content.children) }
+ .toTypedArray()
+
+ override fun thrownExceptionTypes(): Array<out Type> = emptyArray()
+ override fun receiverType(): Type? = receiverNode()?.let { receiver -> TypeAdapter(module, receiver) }
+ override fun flatSignature(): String = node.details(DocumentationNode.Kind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")")
+ override fun signature(): String = node.details(DocumentationNode.Kind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") // TODO it should be FQ types
+
+ override fun parameters(): Array<out Parameter> =
+ ((receiverNode()?.let { receiver -> listOf<Parameter>(ReceiverParameterAdapter(module, receiver, this)) } ?: emptyList())
+ + node.details(DocumentationNode.Kind.Parameter).map { ParameterAdapter(module, it) }
+ ).toTypedArray()
+
+ override fun typeParameters(): Array<out TypeVariable> = node.details(DocumentationNode.Kind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray()
+
+ override fun typeParamTags(): Array<out ParamTag> = node.details(DocumentationNode.Kind.TypeParameter).filter { it.content.summary !is ContentEmpty || it.content.description !is ContentEmpty || it.content.sections.isNotEmpty() }.map {
+ ParamTagAdapter(module, this, it.name, true, it.content.children)
+ }.toTypedArray()
+
+ private fun receiverNode() = node.details(DocumentationNode.Kind.Receiver).let { receivers ->
+ when {
+ receivers.isNotEmpty() -> receivers.single().detail(DocumentationNode.Kind.Type)
+ else -> null
+ }
+ }
+}
+
+class ConstructorAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ExecutableMemberAdapter(module, node), ConstructorDoc {
+ override fun name(): String = node.owner?.name ?: throw IllegalStateException("No owner for $node")
+}
+
+class MethodAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ExecutableMemberDoc by ExecutableMemberAdapter(module, node), MethodDoc {
+ override fun overrides(meth: MethodDoc?): Boolean = false // TODO
+
+ override fun overriddenType(): Type? = node.overrides.firstOrNull()?.owner?.let { owner -> TypeAdapter(module, owner) }
+
+ override fun overriddenMethod(): MethodDoc? = node.overrides.map { MethodAdapter(module, it) }.firstOrNull()
+ override fun overriddenClass(): ClassDoc? = overriddenMethod()?.containingClass()
+
+ override fun isAbstract(): Boolean = false // TODO
+
+ override fun isDefault(): Boolean = false
+
+ override fun returnType(): Type = TypeAdapter(module, node.detail(DocumentationNode.Kind.Type))
+}
+
+class FieldAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc by ProgramElementAdapter(module, node), FieldDoc {
+ override fun isSynthetic(): Boolean = false
+
+ override fun constantValueExpression(): String? = node.details(DocumentationNode.Kind.Value).firstOrNull()?.let { it.name }
+ override fun constantValue(): Any? = constantValueExpression()
+
+ override fun type(): Type = TypeAdapter(module, node.detail(DocumentationNode.Kind.Type))
+ override fun isTransient(): Boolean = node.hasAnnotation(Transient::class)
+ override fun serialFieldTags(): Array<out SerialFieldTag> = emptyArray()
+
+ override fun isVolatile(): Boolean = node.hasAnnotation(Volatile::class)
+}
+
+open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNode: DocumentationNode)
+ : DocumentationNodeAdapter(module, classNode),
+ Type by TypeAdapter(module, classNode),
+ ProgramElementDoc by ProgramElementAdapter(module, classNode),
+ ClassDoc {
+
+ override fun name(): String {
+ val parent = classNode.owner
+ if (parent?.kind in DocumentationNode.Kind.classLike) {
+ return parent!!.name + "." + classNode.name
+ }
+ return classNode.name
+ }
+
+ override fun constructors(filter: Boolean): Array<out ConstructorDoc> = classNode.members(DocumentationNode.Kind.Constructor).map { ConstructorAdapter(module, it) }.toTypedArray()
+ override fun constructors(): Array<out ConstructorDoc> = constructors(true)
+ override fun importedPackages(): Array<out PackageDoc> = emptyArray()
+ override fun importedClasses(): Array<out ClassDoc>? = emptyArray()
+ override fun typeParameters(): Array<out TypeVariable> = classNode.details(DocumentationNode.Kind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray()
+ override fun asTypeVariable(): TypeVariable? = if (classNode.kind == DocumentationNode.Kind.Class) TypeVariableAdapter(module, classNode) else null
+ override fun isExternalizable(): Boolean = interfaces().any { it.qualifiedName() == "java.io.Externalizable" }
+ override fun definesSerializableFields(): Boolean = false
+ override fun methods(filter: Boolean): Array<out MethodDoc> = classNode.members(DocumentationNode.Kind.Function).map { MethodAdapter(module, it) }.toTypedArray() // TODO include get/set methods
+ override fun methods(): Array<out MethodDoc> = methods(true)
+ override fun enumConstants(): Array<out FieldDoc>? = classNode.members(DocumentationNode.Kind.EnumItem).map { FieldAdapter(module, it) }.toTypedArray()
+ override fun isAbstract(): Boolean = classNode.details(DocumentationNode.Kind.Modifier).any { it.name == "abstract" }
+ override fun interfaceTypes(): Array<out Type> = classNode.lookupSuperClasses(module)
+ .filter { it.kind == DocumentationNode.Kind.Interface }
+ .map { ClassDocumentationNodeAdapter(module, it) }
+ .toTypedArray()
+
+ override fun interfaces(): Array<out ClassDoc> = classNode.lookupSuperClasses(module)
+ .filter { it.kind == DocumentationNode.Kind.Interface }
+ .map { ClassDocumentationNodeAdapter(module, it) }
+ .toTypedArray()
+
+ override fun typeParamTags(): Array<out ParamTag> = (classNode.details(DocumentationNode.Kind.TypeParameter).filter { it.content.summary !is ContentEmpty || it.content.description !is ContentEmpty || it.content.sections.isNotEmpty() }.map {
+ ParamTagAdapter(module, this, it.name, true, it.content.children)
+ } + classNode.content.sections.filter { it.subjectName in typeParameters().map { it.simpleTypeName() } }.map {
+ ParamTagAdapter(module, this, it.subjectName ?: "?", true, it.children)
+ }).toTypedArray()
+
+ override fun fields(): Array<out FieldDoc> = fields(true)
+ override fun fields(filter: Boolean): Array<out FieldDoc> = classNode.members(DocumentationNode.Kind.Field).map { FieldAdapter(module, it) }.toTypedArray()
+
+ override fun findClass(className: String?): ClassDoc? = null // TODO !!!
+ override fun serializableFields(): Array<out FieldDoc> = emptyArray()
+ override fun superclassType(): Type? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == DocumentationNode.Kind.Class }?.let { ClassDocumentationNodeAdapter(module, it) }
+ override fun serializationMethods(): Array<out MethodDoc> = emptyArray() // TODO
+ override fun superclass(): ClassDoc? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == DocumentationNode.Kind.Class }?.let { ClassDocumentationNodeAdapter(module, it) }
+ override fun isSerializable(): Boolean = false // TODO
+ override fun subclassOf(cd: ClassDoc?): Boolean {
+ if (cd == null) {
+ return false
+ }
+
+ val expectedFQName = cd.qualifiedName()
+ val types = arrayListOf(classNode)
+ val visitedTypes = HashSet<String>()
+
+ while (types.isNotEmpty()) {
+ val type = types.removeAt(types.lastIndex)
+ val fqName = type.qualifiedName()
+
+ if (expectedFQName == fqName) {
+ return true
+ }
+
+ visitedTypes.add(fqName)
+ types.addAll(type.details(DocumentationNode.Kind.Supertype).filter { it.qualifiedName() !in visitedTypes })
+ }
+
+ return false
+ }
+
+ override fun innerClasses(): Array<out ClassDoc> = classNode.members(DocumentationNode.Kind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray()
+ override fun innerClasses(filter: Boolean): Array<out ClassDoc> = innerClasses()
+}
+
+fun DocumentationNode.lookupSuperClasses(module: ModuleNodeAdapter) =
+ details(DocumentationNode.Kind.Supertype)
+ .map { it.links.firstOrNull() }
+ .map { module.allTypes[it?.qualifiedName()] }
+ .filterNotNull()
+
+fun List<DocumentationNode>.collectAllTypesRecursively(): Map<String, DocumentationNode> {
+ val result = hashMapOf<String, DocumentationNode>()
+
+ fun DocumentationNode.collectTypesRecursively() {
+ val classLikeMembers = DocumentationNode.Kind.classLike.flatMap { members(it) }
+ classLikeMembers.forEach {
+ result.put(it.qualifiedName(), it)
+ it.collectTypesRecursively()
+ }
+ }
+
+ forEach { it.collectTypesRecursively() }
+ return result
+}
+
+class ModuleNodeAdapter(val module: DocumentationModule, val reporter: DocErrorReporter, val outputPath: String) : DocumentationNodeBareAdapter(module), DocErrorReporter by reporter, RootDoc {
+ val allPackages = module.members(DocumentationNode.Kind.Package).toMapBy { it.name }
+ val allTypes = module.members(DocumentationNode.Kind.Package).collectAllTypesRecursively()
+
+ override fun packageNamed(name: String?): PackageDoc? = allPackages[name]?.let { PackageAdapter(this, it) }
+
+ override fun classes(): Array<out ClassDoc> =
+ allTypes.values.map { ClassDocumentationNodeAdapter(this, it) }.toTypedArray()
+
+ override fun options(): Array<out Array<String>> = arrayOf(
+ arrayOf("-d", outputPath),
+ arrayOf("-docencoding", "UTF-8"),
+ arrayOf("-charset", "UTF-8"),
+ arrayOf("-keywords")
+ )
+
+ override fun specifiedPackages(): Array<out PackageDoc>? = module.members(DocumentationNode.Kind.Package).map { PackageAdapter(this, it) }.toTypedArray()
+
+ override fun classNamed(qualifiedName: String?): ClassDoc? =
+ allTypes[qualifiedName]?.let { ClassDocumentationNodeAdapter(this, it) }
+
+ override fun specifiedClasses(): Array<out ClassDoc> = classes()
+}
diff --git a/core/src/main/kotlin/javadoc/dokka-adapters.kt b/core/src/main/kotlin/javadoc/dokka-adapters.kt
new file mode 100644
index 00000000..23ee1702
--- /dev/null
+++ b/core/src/main/kotlin/javadoc/dokka-adapters.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka.javadoc
+
+import com.google.inject.Inject
+import com.sun.tools.doclets.formats.html.HtmlDoclet
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.FormatDescriptor
+
+class JavadocGenerator @Inject constructor (val options: DocumentationOptions, 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))
+ }
+
+ override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
+ // no outline could be generated separately
+ }
+
+ override fun buildSupportFiles() {
+ }
+}
+
+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
+}
diff --git a/core/src/main/kotlin/javadoc/reporter.kt b/core/src/main/kotlin/javadoc/reporter.kt
new file mode 100644
index 00000000..fc38368c
--- /dev/null
+++ b/core/src/main/kotlin/javadoc/reporter.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.dokka.javadoc
+
+import com.sun.javadoc.DocErrorReporter
+import com.sun.javadoc.SourcePosition
+import org.jetbrains.dokka.DokkaLogger
+
+class StandardReporter(val logger: DokkaLogger) : DocErrorReporter {
+ override fun printWarning(msg: String?) {
+ logger.warn(msg.toString())
+ }
+
+ override fun printWarning(pos: SourcePosition?, msg: String?) {
+ logger.warn(format(pos, msg))
+ }
+
+ override fun printError(msg: String?) {
+ logger.error(msg.toString())
+ }
+
+ override fun printError(pos: SourcePosition?, msg: String?) {
+ logger.error(format(pos, msg))
+ }
+
+ override fun printNotice(msg: String?) {
+ logger.info(msg.toString())
+ }
+
+ override fun printNotice(pos: SourcePosition?, msg: String?) {
+ logger.info(format(pos, msg))
+ }
+
+ private fun format(pos: SourcePosition?, msg: String?) =
+ if (pos == null) msg.toString() else "${pos.file()}:${pos.line()}:${pos.column()}: $msg"
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/javadoc/source-position.kt b/core/src/main/kotlin/javadoc/source-position.kt
new file mode 100644
index 00000000..0e4c6e3c
--- /dev/null
+++ b/core/src/main/kotlin/javadoc/source-position.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.dokka.javadoc
+
+import com.sun.javadoc.SourcePosition
+import org.jetbrains.dokka.DocumentationNode
+import java.io.File
+
+class SourcePositionAdapter(val docNode: DocumentationNode) : SourcePosition {
+
+ private val sourcePositionParts: List<String> by lazy {
+ docNode.details(DocumentationNode.Kind.SourcePosition).firstOrNull()?.name?.split(":") ?: emptyList()
+ }
+
+ override fun file(): File? = if (sourcePositionParts.isEmpty()) null else File(sourcePositionParts[0])
+
+ override fun line(): Int = sourcePositionParts.getOrNull(1)?.toInt() ?: -1
+
+ override fun column(): Int = sourcePositionParts.getOrNull(2)?.toInt() ?: -1
+}
diff --git a/core/src/main/kotlin/javadoc/tags.kt b/core/src/main/kotlin/javadoc/tags.kt
new file mode 100644
index 00000000..5872dbaa
--- /dev/null
+++ b/core/src/main/kotlin/javadoc/tags.kt
@@ -0,0 +1,214 @@
+package org.jetbrains.dokka.javadoc
+
+import com.sun.javadoc.*
+import org.jetbrains.dokka.*
+import java.util.*
+
+class TagImpl(val holder: Doc, val name: String, val text: String): Tag {
+ override fun text(): String? = text
+
+ override fun holder(): Doc = holder
+ override fun firstSentenceTags(): Array<out Tag>? = arrayOf()
+ override fun inlineTags(): Array<out Tag>? = arrayOf()
+
+ override fun name(): String = name
+ override fun kind(): String = name
+
+ override fun position(): SourcePosition = holder.position()
+}
+
+class TextTag(val holder: Doc, val content: ContentText) : Tag {
+ val plainText: String
+ get() = content.text
+
+ override fun name(): String = "Text"
+ override fun kind(): String = name()
+ override fun text(): String? = plainText
+ override fun inlineTags(): Array<out Tag> = arrayOf(this)
+ override fun holder(): Doc = holder
+ override fun firstSentenceTags(): Array<out Tag> = arrayOf(this)
+ override fun position(): SourcePosition = holder.position()
+}
+
+abstract class SeeTagAdapter(val holder: Doc, val content: ContentNodeLink) : SeeTag {
+ override fun position(): SourcePosition? = holder.position()
+ override fun name(): String = "@see"
+ override fun kind(): String = "@see"
+ override fun holder(): Doc = holder
+
+ override fun text(): String? = content.node?.name ?: "(?)"
+}
+
+class SeeExternalLinkTagAdapter(val holder: Doc, val link: ContentExternalLink) : SeeTag {
+ override fun position(): SourcePosition = holder.position()
+ override fun text(): String = label()
+ override fun inlineTags(): Array<out Tag> = emptyArray() // TODO
+
+ override fun label(): String {
+ val label = link.asText() ?: link.href
+ return "<a href=\"${link.href}\">$label</a>"
+ }
+
+ override fun referencedPackage(): PackageDoc? = null
+ override fun referencedClass(): ClassDoc? = null
+ override fun referencedMemberName(): String? = null
+ override fun referencedClassName(): String? = null
+ override fun referencedMember(): MemberDoc? = null
+ override fun holder(): Doc = holder
+ override fun firstSentenceTags(): Array<out Tag> = inlineTags()
+ override fun name(): String = "@link"
+ override fun kind(): String = "@see"
+}
+
+fun ContentBlock.asText(): String? {
+ val contentText = children.singleOrNull() as? ContentText
+ return contentText?.text
+}
+
+class SeeMethodTagAdapter(holder: Doc, val method: MethodAdapter, content: ContentNodeLink) : SeeTagAdapter(holder, content) {
+ override fun referencedMember(): MemberDoc = method
+ override fun referencedMemberName(): String = method.name()
+ override fun referencedPackage(): PackageDoc? = null
+ override fun referencedClass(): ClassDoc = method.containingClass()
+ override fun referencedClassName(): String = method.containingClass().name()
+ override fun label(): String = "${method.containingClass().name()}.${method.name()}"
+
+ override fun inlineTags(): Array<out Tag> = emptyArray() // TODO
+ override fun firstSentenceTags(): Array<out Tag> = inlineTags() // TODO
+}
+
+class SeeClassTagAdapter(holder: Doc, val clazz: ClassDocumentationNodeAdapter, content: ContentNodeLink) : SeeTagAdapter(holder, content) {
+ override fun referencedMember(): MemberDoc? = null
+ override fun referencedMemberName(): String? = null
+ override fun referencedPackage(): PackageDoc? = null
+ override fun referencedClass(): ClassDoc = clazz
+ override fun referencedClassName(): String = clazz.name()
+ override fun label(): String = "${clazz.classNode.kind.name.toLowerCase()} ${clazz.name()}"
+
+ override fun inlineTags(): Array<out Tag> = emptyArray() // TODO
+ override fun firstSentenceTags(): Array<out Tag> = inlineTags() // TODO
+}
+
+class ParamTagAdapter(val module: ModuleNodeAdapter,
+ val holder: Doc,
+ val parameterName: String,
+ val typeParameter: Boolean,
+ val content: List<ContentNode>) : ParamTag {
+
+ constructor(module: ModuleNodeAdapter, holder: Doc, parameterName: String, isTypeParameter: Boolean, content: ContentNode)
+ : this(module, holder, parameterName, isTypeParameter, listOf(content)) {
+ }
+
+ override fun name(): String = "@param"
+ override fun kind(): String = name()
+ override fun holder(): Doc = holder
+ override fun position(): SourcePosition? = holder.position()
+
+ override fun text(): String = "@param $parameterName ..."
+ override fun inlineTags(): Array<out Tag> = content.flatMap { buildInlineTags(module, holder, it) }.toTypedArray()
+ override fun firstSentenceTags(): Array<out Tag> = arrayOf(TextTag(holder, ContentText(text())))
+
+ override fun isTypeParameter(): Boolean = typeParameter
+ override fun parameterComment(): String = content.toString() // TODO
+ override fun parameterName(): String = parameterName
+}
+
+
+class ThrowsTagAdapter(val holder: Doc, val type: ClassDocumentationNodeAdapter) : ThrowsTag {
+ override fun name(): String = "@throws"
+ override fun kind(): String = name()
+ override fun holder(): Doc = holder
+ override fun position(): SourcePosition? = holder.position()
+
+ override fun text(): String = "@throws ${type.qualifiedTypeName()}"
+ override fun inlineTags(): Array<out Tag> = emptyArray()
+ override fun firstSentenceTags(): Array<out Tag> = emptyArray()
+
+ override fun exceptionComment(): String = ""
+ override fun exceptionType(): Type = type
+ override fun exception(): ClassDoc = type
+ override fun exceptionName(): String = type.qualifiedName()
+}
+
+fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, root: ContentNode): List<Tag> = ArrayList<Tag>().let { buildInlineTags(module, holder, root, it); it }
+
+private fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, nodes: List<ContentNode>, result: MutableList<Tag>) {
+ nodes.forEach {
+ buildInlineTags(module, holder, it, result)
+ }
+}
+
+
+private fun buildInlineTags(module: ModuleNodeAdapter, holder: Doc, node: ContentNode, result: MutableList<Tag>) {
+ fun surroundWith(module: ModuleNodeAdapter, holder: Doc, prefix: String, postfix: String, node: ContentBlock, result: MutableList<Tag>) {
+ if (node.children.isNotEmpty()) {
+ val open = TextTag(holder, ContentText(prefix))
+ val close = TextTag(holder, ContentText(postfix))
+
+ result.add(open)
+ buildInlineTags(module, holder, node.children, result)
+
+ if (result.last() === open) {
+ result.removeAt(result.lastIndex)
+ } else {
+ result.add(close)
+ }
+ }
+ }
+
+ fun surroundWith(module: ModuleNodeAdapter, holder: Doc, prefix: String, postfix: String, node: ContentNode, result: MutableList<Tag>) {
+ if (node !is ContentEmpty) {
+ val open = TextTag(holder, ContentText(prefix))
+ val close = TextTag(holder, ContentText(postfix))
+
+ result.add(open)
+ buildInlineTags(module, holder, node, result)
+ if (result.last() === open) {
+ result.removeAt(result.lastIndex)
+ } else {
+ result.add(close)
+ }
+ }
+ }
+
+ when (node) {
+ is ContentText -> result.add(TextTag(holder, node))
+ is ContentNodeLink -> {
+ val target = node.node
+ when (target?.kind) {
+ DocumentationNode.Kind.Function -> result.add(SeeMethodTagAdapter(holder, MethodAdapter(module, node.node!!), node))
+
+ in DocumentationNode.Kind.classLike -> result.add(SeeClassTagAdapter(holder, ClassDocumentationNodeAdapter(module, node.node!!), node))
+
+ else -> buildInlineTags(module, holder, node.children, result)
+ }
+ }
+ is ContentExternalLink -> result.add(SeeExternalLinkTagAdapter(holder, node))
+ is ContentCode -> surroundWith(module, holder, "<code>", "</code>", node, result)
+ is ContentBlockCode -> surroundWith(module, holder, "<code><pre>", "</pre></code>", node, result)
+ is ContentEmpty -> {}
+ is ContentEmphasis -> surroundWith(module, holder, "<em>", "</em>", node, result)
+ is ContentHeading -> surroundWith(module, holder, "<h${node.level}>", "</h${node.level}>", node, result)
+ is ContentEntity -> result.add(TextTag(holder, ContentText(node.text))) // TODO ??
+ is ContentIdentifier -> result.add(TextTag(holder, ContentText(node.text))) // TODO
+ is ContentKeyword -> result.add(TextTag(holder, ContentText(node.text))) // TODO
+ is ContentListItem -> surroundWith(module, holder, "<li>", "</li>", node, result)
+ is ContentOrderedList -> surroundWith(module, holder, "<ol>", "</ol>", node, result)
+ is ContentUnorderedList -> surroundWith(module, holder, "<ul>", "</ul>", node, result)
+ is ContentParagraph -> surroundWith(module, holder, "<p>", "</p>", node, result)
+ is ContentSection -> surroundWith(module, holder, "<p>", "</p>", node, result) // TODO how section should be represented?
+ is ContentNonBreakingSpace -> result.add(TextTag(holder, ContentText("&nbsp;")))
+ is ContentStrikethrough -> surroundWith(module, holder, "<strike>", "</strike>", node, result)
+ is ContentStrong -> surroundWith(module, holder, "<strong>", "</strong>", node, result)
+ is ContentSymbol -> result.add(TextTag(holder, ContentText(node.text))) // TODO?
+ is Content -> {
+ surroundWith(module, holder, "<p>", "</p>", node.summary, result)
+ surroundWith(module, holder, "<p>", "</p>", node.description, result)
+// node.sections.forEach {
+// buildInlineTags(module, holder, it, result)
+// }
+ }
+
+ else -> result.add(TextTag(holder, ContentText("$node")))
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/main.kt b/core/src/main/kotlin/main.kt
new file mode 100644
index 00000000..22e82991
--- /dev/null
+++ b/core/src/main/kotlin/main.kt
@@ -0,0 +1,262 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Guice
+import com.google.inject.Injector
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiManager
+import com.sampullara.cli.Args
+import com.sampullara.cli.Argument
+import org.jetbrains.dokka.Utilities.DokkaModule
+import org.jetbrains.kotlin.cli.common.arguments.ValueDescription
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzerForTopLevel
+import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
+import org.jetbrains.kotlin.utils.PathUtil
+import java.io.File
+import kotlin.util.measureTimeMillis
+
+class DokkaArguments {
+ @set:Argument(value = "src", description = "Source file or directory (allows many paths separated by the system path separator)")
+ @ValueDescription("<path>")
+ public var src: String = ""
+
+ @set:Argument(value = "srcLink", description = "Mapping between a source directory and a Web site for browsing the code")
+ @ValueDescription("<path>=<url>[#lineSuffix]")
+ public var srcLink: String = ""
+
+ @set:Argument(value = "include", description = "Markdown files to load (allows many paths separated by the system path separator)")
+ @ValueDescription("<path>")
+ public var include: String = ""
+
+ @set:Argument(value = "samples", description = "Source root for samples")
+ @ValueDescription("<path>")
+ public var samples: String = ""
+
+ @set:Argument(value = "output", description = "Output directory path")
+ @ValueDescription("<path>")
+ public var outputDir: String = "out/doc/"
+
+ @set:Argument(value = "format", description = "Output format (text, html, markdown, jekyll, kotlin-website)")
+ @ValueDescription("<name>")
+ public var outputFormat: String = "html"
+
+ @set:Argument(value = "module", description = "Name of the documentation module")
+ @ValueDescription("<name>")
+ public var moduleName: String = ""
+
+ @set:Argument(value = "classpath", description = "Classpath for symbol resolution")
+ @ValueDescription("<path>")
+ public var classpath: String = ""
+
+ @set:Argument(value = "nodeprecated", description = "Exclude deprecated members from documentation")
+ public var nodeprecated: Boolean = false
+
+}
+
+private fun parseSourceLinkDefinition(srcLink: String): SourceLinkDefinition {
+ val (path, urlAndLine) = srcLink.split('=')
+ return SourceLinkDefinition(File(path).absolutePath,
+ urlAndLine.substringBefore("#"),
+ urlAndLine.substringAfter("#", "").let { if (it.isEmpty()) null else "#" + it })
+}
+
+public fun main(args: Array<String>) {
+ val arguments = DokkaArguments()
+ val freeArgs: List<String> = Args.parse(arguments, args) ?: listOf()
+ val sources = if (arguments.src.isNotEmpty()) arguments.src.split(File.pathSeparatorChar).toList() + freeArgs else freeArgs
+ val samples = if (arguments.samples.isNotEmpty()) arguments.samples.split(File.pathSeparatorChar).toList() else listOf()
+ val includes = if (arguments.include.isNotEmpty()) arguments.include.split(File.pathSeparatorChar).toList() else listOf()
+
+ val sourceLinks = if (arguments.srcLink.isNotEmpty() && arguments.srcLink.contains("="))
+ listOf(parseSourceLinkDefinition(arguments.srcLink))
+ else {
+ if (arguments.srcLink.isNotEmpty()) {
+ println("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.")
+ }
+ listOf()
+ }
+
+ val classPath = arguments.classpath.split(File.pathSeparatorChar).toList()
+ val generator = DokkaGenerator(
+ DokkaConsoleLogger,
+ classPath,
+ sources,
+ samples,
+ includes,
+ arguments.moduleName,
+ arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' },
+ arguments.outputFormat,
+ sourceLinks,
+ arguments.nodeprecated)
+
+ generator.generate()
+ DokkaConsoleLogger.report()
+}
+
+interface DokkaLogger {
+ fun info(message: String)
+ fun warn(message: String)
+ fun error(message: String)
+}
+
+object DokkaConsoleLogger: DokkaLogger {
+ var warningCount: Int = 0
+
+ override fun info(message: String) = println(message)
+ override fun warn(message: String) {
+ println("WARN: $message")
+ warningCount++
+ }
+
+ override fun error(message: String) = println("ERROR: $message")
+
+ fun report() {
+ if (warningCount > 0) {
+ println("generation completed with $warningCount warnings")
+ } else {
+ println("generation completed successfully")
+ }
+ }
+}
+
+class DokkaMessageCollector(val logger: DokkaLogger): MessageCollector {
+ override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
+ logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location))
+ }
+}
+
+class DokkaGenerator(val logger: DokkaLogger,
+ val classpath: List<String>,
+ val sources: List<String>,
+ val samples: List<String>,
+ val includes: List<String>,
+ val moduleName: String,
+ val outputDir: String,
+ val outputFormat: String,
+ val sourceLinks: List<SourceLinkDefinition>,
+ val skipDeprecated: Boolean = false) {
+ fun generate() {
+ val environment = createAnalysisEnvironment()
+
+ logger.info("Module: $moduleName")
+ logger.info("Output: ${File(outputDir)}")
+ logger.info("Sources: ${environment.sources.joinToString()}")
+ logger.info("Classpath: ${environment.classpath.joinToString()}")
+
+ logger.info("Analysing sources and libraries... ")
+ val startAnalyse = System.currentTimeMillis()
+
+ val options = DocumentationOptions(outputDir, outputFormat, false, sourceLinks = sourceLinks, skipDeprecated = skipDeprecated)
+
+ val injector = Guice.createInjector(DokkaModule(environment, options, logger))
+
+ val documentation = buildDocumentationModule(injector, moduleName, { isSample(it) }, includes)
+
+ val timeAnalyse = System.currentTimeMillis() - startAnalyse
+ logger.info("done in ${timeAnalyse / 1000} secs")
+
+ val timeBuild = measureTimeMillis {
+ logger.info("Generating pages... ")
+ injector.getInstance(Generator::class.java).buildAll(documentation)
+ }
+ logger.info("done in ${timeBuild / 1000} secs")
+
+ Disposer.dispose(environment)
+ }
+
+ fun createAnalysisEnvironment(): AnalysisEnvironment {
+ val environment = AnalysisEnvironment(DokkaMessageCollector(logger))
+
+ environment.apply {
+ addClasspath(PathUtil.getJdkClassesRoots())
+ // addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
+ for (element in this@DokkaGenerator.classpath) {
+ addClasspath(File(element))
+ }
+
+ addSources(this@DokkaGenerator.sources)
+ addSources(this@DokkaGenerator.samples)
+ }
+
+ return environment
+ }
+
+ fun isSample(file: PsiFile): Boolean {
+ val sourceFile = File(file.virtualFile!!.path)
+ return samples.none { sample ->
+ val canonicalSample = File(sample).canonicalPath
+ val canonicalSource = sourceFile.canonicalPath
+ canonicalSource.startsWith(canonicalSample)
+ }
+ }
+}
+
+fun buildDocumentationModule(injector: Injector,
+ moduleName: String,
+ filesToDocumentFilter: (PsiFile) -> Boolean = { file -> true },
+ includes: List<String> = listOf()): DocumentationModule {
+
+ val coreEnvironment = injector.getInstance(KotlinCoreEnvironment::class.java)
+ val fragmentFiles = coreEnvironment.getSourceFiles().filter(filesToDocumentFilter)
+
+ val resolutionFacade = injector.getInstance(DokkaResolutionFacade::class.java)
+ val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzerForTopLevel::class.java)
+ analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles)
+
+ val fragments = fragmentFiles
+ .map { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) }
+ .filterNotNull()
+ .distinct()
+
+ val packageDocs = injector.getInstance(PackageDocs::class.java)
+ for (include in includes) {
+ packageDocs.parse(include, fragments.firstOrNull())
+ }
+ val documentationModule = DocumentationModule(moduleName, packageDocs.moduleContent)
+
+ with(injector.getInstance(DocumentationBuilder::class.java)) {
+ documentationModule.appendFragments(fragments, packageDocs.packageContent,
+ injector.getInstance(PackageDocumentationBuilder::class.java))
+ }
+
+ val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter)
+ with(injector.getInstance(JavaDocumentationBuilder::class.java)) {
+ javaFiles.map { appendFile(it, documentationModule, packageDocs.packageContent) }
+ }
+
+ injector.getInstance(NodeReferenceGraph::class.java).resolveReferences()
+
+ return documentationModule
+}
+
+
+fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> {
+ val sourceRoots = configuration.get(CommonConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<JavaSourceRoot>()
+ ?.map { it.file }
+ ?: listOf()
+
+ val result = arrayListOf<PsiJavaFile>()
+ val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
+ sourceRoots.forEach { sourceRoot ->
+ sourceRoot.absoluteFile.walkTopDown().forEach {
+ val vFile = localFileSystem.findFileByPath(it.path)
+ if (vFile != null) {
+ val psiFile = PsiManager.getInstance(project).findFile(vFile)
+ if (psiFile is PsiJavaFile) {
+ result.add(psiFile)
+ }
+ }
+ }
+ }
+ return result
+}
diff --git a/core/src/main/resources/META-INF/MANIFEST.MF b/core/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..78fabddc
--- /dev/null
+++ b/core/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Class-Path: kotlin-plugin.jar
+Main-Class: org.jetbrains.dokka.DokkaPackage
+
diff --git a/core/src/main/resources/dokka-antlib.xml b/core/src/main/resources/dokka-antlib.xml
new file mode 100644
index 00000000..9c3373d5
--- /dev/null
+++ b/core/src/main/resources/dokka-antlib.xml
@@ -0,0 +1,3 @@
+<antlib>
+ <taskdef name="dokka" classname="org.jetbrains.dokka.ant.DokkaAntTask"/>
+</antlib>
diff --git a/core/src/main/resources/dokka/format/html-as-java.properties b/core/src/main/resources/dokka/format/html-as-java.properties
new file mode 100644
index 00000000..f598f377
--- /dev/null
+++ b/core/src/main/resources/dokka/format/html-as-java.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.HtmlAsJavaFormatDescriptor
+description=Produces output in HTML format using Java syntax \ No newline at end of file
diff --git a/core/src/main/resources/dokka/format/html.properties b/core/src/main/resources/dokka/format/html.properties
new file mode 100644
index 00000000..7881dfae
--- /dev/null
+++ b/core/src/main/resources/dokka/format/html.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.HtmlFormatDescriptor
+description=Produces output in HTML format \ 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
new file mode 100644
index 00000000..a58317fc
--- /dev/null
+++ b/core/src/main/resources/dokka/format/javadoc.properties
@@ -0,0 +1 @@
+class=org.jetbrains.dokka.javadoc.JavadocFormatDescriptor \ No newline at end of file
diff --git a/core/src/main/resources/dokka/format/jekyll.properties b/core/src/main/resources/dokka/format/jekyll.properties
new file mode 100644
index 00000000..b11401a4
--- /dev/null
+++ b/core/src/main/resources/dokka/format/jekyll.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.JekyllFormatDescriptor
+description=Produces documentation in Jekyll format \ No newline at end of file
diff --git a/core/src/main/resources/dokka/format/kotlin-website.properties b/core/src/main/resources/dokka/format/kotlin-website.properties
new file mode 100644
index 00000000..c13e7675
--- /dev/null
+++ b/core/src/main/resources/dokka/format/kotlin-website.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.KotlinWebsiteFormatDescriptor
+description=Generates Kotlin website documentation \ No newline at end of file
diff --git a/core/src/main/resources/dokka/format/markdown.properties b/core/src/main/resources/dokka/format/markdown.properties
new file mode 100644
index 00000000..6217a6df
--- /dev/null
+++ b/core/src/main/resources/dokka/format/markdown.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.MarkdownFormatDescriptor
+description=Produces documentation in markdown format \ 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
new file mode 100644
index 00000000..a4a16200
--- /dev/null
+++ b/core/src/main/resources/dokka/generator/default.properties
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 00000000..4075704f
--- /dev/null
+++ b/core/src/main/resources/dokka/generator/javadoc.properties
@@ -0,0 +1,2 @@
+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/language/java.properties b/core/src/main/resources/dokka/language/java.properties
new file mode 100644
index 00000000..ab42f532
--- /dev/null
+++ b/core/src/main/resources/dokka/language/java.properties
@@ -0,0 +1 @@
+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
new file mode 100644
index 00000000..16092007
--- /dev/null
+++ b/core/src/main/resources/dokka/language/kotlin.properties
@@ -0,0 +1 @@
+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
new file mode 100644
index 00000000..7268af37
--- /dev/null
+++ b/core/src/main/resources/dokka/outline/yaml.properties
@@ -0,0 +1 @@
+class=org.jetbrains.dokka.YamlOutlineService \ No newline at end of file
diff --git a/core/src/main/resources/dokka/styles/style.css b/core/src/main/resources/dokka/styles/style.css
new file mode 100644
index 00000000..09586237
--- /dev/null
+++ b/core/src/main/resources/dokka/styles/style.css
@@ -0,0 +1,280 @@
+@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700);
+
+body, table {
+ padding:50px;
+ font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color:#555;
+ font-weight:300;
+}
+
+.keyword {
+ color:black;
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ font-size:12px;
+}
+
+.symbol {
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ font-size:12px;
+}
+
+.identifier {
+ color: darkblue;
+ font-size:12px;
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color:#222;
+ margin:0 0 20px;
+}
+
+p, ul, ol, table, pre, dl {
+ margin:0 0 20px;
+}
+
+h1, h2, h3 {
+ line-height:1.1;
+}
+
+h1 {
+ font-size:28px;
+}
+
+h2 {
+ color:#393939;
+}
+
+h3, h4, h5, h6 {
+ color:#494949;
+}
+
+a {
+ color:#258aaf;
+ font-weight:400;
+ text-decoration:none;
+}
+
+a:hover {
+ color: inherit;
+ text-decoration:underline;
+}
+
+a small {
+ font-size:11px;
+ color:#555;
+ margin-top:-0.6em;
+ display:block;
+}
+
+.wrapper {
+ width:860px;
+ margin:0 auto;
+}
+
+blockquote {
+ border-left:1px solid #e5e5e5;
+ margin:0;
+ padding:0 0 0 20px;
+ font-style:italic;
+}
+
+code, pre {
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ color:#333;
+ font-size:12px;
+}
+
+pre {
+ display: block;
+/*
+ padding:8px 8px;
+ background: #f8f8f8;
+ border-radius:5px;
+ border:1px solid #e5e5e5;
+*/
+ overflow-x: auto;
+}
+
+table {
+ width:100%;
+ border-collapse:collapse;
+}
+
+th, td {
+ text-align:left;
+ vertical-align: top;
+ padding:5px 10px;
+}
+
+dt {
+ color:#444;
+ font-weight:700;
+}
+
+th {
+ color:#444;
+}
+
+img {
+ max-width:100%;
+}
+
+header {
+ width:270px;
+ float:left;
+ position:fixed;
+}
+
+header ul {
+ list-style:none;
+ height:40px;
+
+ padding:0;
+
+ background: #eee;
+ background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
+ background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+
+ border-radius:5px;
+ border:1px solid #d2d2d2;
+ box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
+ width:270px;
+}
+
+header li {
+ width:89px;
+ float:left;
+ border-right:1px solid #d2d2d2;
+ height:40px;
+}
+
+header ul a {
+ line-height:1;
+ font-size:11px;
+ color:#999;
+ display:block;
+ text-align:center;
+ padding-top:6px;
+ height:40px;
+}
+
+strong {
+ color:#222;
+ font-weight:700;
+}
+
+header ul li + li {
+ width:88px;
+ border-left:1px solid #fff;
+}
+
+header ul li + li + li {
+ border-right:none;
+ width:89px;
+}
+
+header ul a strong {
+ font-size:14px;
+ display:block;
+ color:#222;
+}
+
+section {
+ width:500px;
+ float:right;
+ padding-bottom:50px;
+}
+
+small {
+ font-size:11px;
+}
+
+hr {
+ border:0;
+ background:#e5e5e5;
+ height:1px;
+ margin:0 0 20px;
+}
+
+footer {
+ width:270px;
+ float:left;
+ position:fixed;
+ bottom:50px;
+}
+
+@media print, screen and (max-width: 960px) {
+
+ div.wrapper {
+ width:auto;
+ margin:0;
+ }
+
+ header, section, footer {
+ float:none;
+ position:static;
+ width:auto;
+ }
+
+ header {
+ padding-right:320px;
+ }
+
+ section {
+ border:1px solid #e5e5e5;
+ border-width:1px 0;
+ padding:20px 0;
+ margin:0 0 20px;
+ }
+
+ header a small {
+ display:inline;
+ }
+
+ header ul {
+ position:absolute;
+ right:50px;
+ top:52px;
+ }
+}
+
+@media print, screen and (max-width: 720px) {
+ body {
+ word-wrap:break-word;
+ }
+
+ header {
+ padding:0;
+ }
+
+ header ul, header p.view {
+ position:static;
+ }
+
+ pre, code {
+ word-wrap:normal;
+ }
+}
+
+@media print, screen and (max-width: 480px) {
+ body {
+ padding:15px;
+ }
+
+ header ul {
+ display:none;
+ }
+}
+
+@media print {
+ body {
+ padding:0.4in;
+ font-size:12pt;
+ color:#444;
+ }
+}
diff --git a/core/src/main/resources/format/javadoc.properties b/core/src/main/resources/format/javadoc.properties
new file mode 100644
index 00000000..a58317fc
--- /dev/null
+++ b/core/src/main/resources/format/javadoc.properties
@@ -0,0 +1 @@
+class=org.jetbrains.dokka.javadoc.JavadocFormatDescriptor \ No newline at end of file