From 49439594f86217d8a25e8df2580b8ef29d836230 Mon Sep 17 00:00:00 2001
From: Paweł Marks <pmarks@virtuslab.com>
Date: Tue, 26 Nov 2019 13:44:27 +0100
Subject: Introduction of all important extension points and restructuring of
 DokkaGenerator

---
 core/src/main/kotlin/CoreExtensions.kt             |  35 +++-
 core/src/main/kotlin/DokkaDescriptorVisitor.kt     | 191 -----------------
 core/src/main/kotlin/DokkaGenerator.kt             | 183 +++++++++++-----
 core/src/main/kotlin/Model/DocumentationNode.kt    |   2 +-
 .../transformers/DocumentationNodeTransformer.kt   |   8 -
 .../Model/transformers/DocumentationNodesMerger.kt |  90 --------
 .../pages/DefaultMarkdownToContentConverter.kt     | 229 +++++++++++++++++++++
 .../kotlin/pages/MarkdownToContentConverter.kt     | 219 +-------------------
 .../pages/transformers/PageNodeTransformer.kt      |   7 -
 .../main/kotlin/plugability/DefaultExtensions.kt   |  25 +++
 core/src/main/kotlin/plugability/DokkaContext.kt   |  61 ++++--
 core/src/main/kotlin/plugability/extensions.kt     |   2 +-
 core/src/main/kotlin/renderers/DefaultRenderer.kt  |   7 +-
 core/src/main/kotlin/renderers/HtmlRenderer.kt     |   7 +-
 core/src/main/kotlin/renderers/Renderer.kt         |   1 +
 .../kotlin/resolvers/DefaultLocationProvider.kt    |  11 +-
 .../DefaultDocumentationToPageTransformer.kt       |  20 --
 .../transformers/DocumentationToPageTransformer.kt |  11 -
 .../kotlin/transformers/PageNodeTransformer.kt     |   8 -
 .../DefaultDescriptorToDocumentationTranslator.kt  | 208 +++++++++++++++++++
 .../DescriptorToDocumentationTranslator.kt         |  15 ++
 .../DefaultDocumentationNodeMerger.kt              |  86 ++++++++
 .../DefaultDocumentationToPageTranslator.kt        |  24 +++
 .../documentation/DocumentationNodeMerger.kt       |   8 +
 .../documentation/DocumentationNodeTransformer.kt  |   8 +
 .../documentation/DocumentationToPageTranslator.kt |  12 ++
 .../transformers/pages/PageNodeTransformer.kt      |   8 +
 27 files changed, 853 insertions(+), 633 deletions(-)
 delete mode 100644 core/src/main/kotlin/DokkaDescriptorVisitor.kt
 delete mode 100644 core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt
 delete mode 100644 core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
 create mode 100644 core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt
 delete mode 100644 core/src/main/kotlin/pages/transformers/PageNodeTransformer.kt
 create mode 100644 core/src/main/kotlin/plugability/DefaultExtensions.kt
 delete mode 100644 core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
 delete mode 100644 core/src/main/kotlin/transformers/DocumentationToPageTransformer.kt
 delete mode 100644 core/src/main/kotlin/transformers/PageNodeTransformer.kt
 create mode 100644 core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt
 create mode 100644 core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt
 create mode 100644 core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt
 create mode 100644 core/src/main/kotlin/transformers/documentation/DefaultDocumentationToPageTranslator.kt
 create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationNodeMerger.kt
 create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationNodeTransformer.kt
 create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationToPageTranslator.kt
 create mode 100644 core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt

(limited to 'core/src')

diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt
index 6579cab2..f56cd854 100644
--- a/core/src/main/kotlin/CoreExtensions.kt
+++ b/core/src/main/kotlin/CoreExtensions.kt
@@ -1,14 +1,37 @@
 package org.jetbrains.dokka
 
-import org.jetbrains.dokka.Model.transformers.DocumentationNodeTransformer
+import org.jetbrains.dokka.pages.MarkdownToContentConverter
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.plugability.DokkaContext
 import org.jetbrains.dokka.plugability.ExtensionPoint
+import org.jetbrains.dokka.renderers.FileWriter
 import org.jetbrains.dokka.renderers.Renderer
 import org.jetbrains.dokka.resolvers.LocationProvider
-import org.jetbrains.dokka.transformers.PageNodeTransformer
+import org.jetbrains.dokka.transformers.descriptors.DescriptorToDocumentationTranslator
+import org.jetbrains.dokka.transformers.documentation.DocumentationNodeMerger
+import org.jetbrains.dokka.transformers.documentation.DocumentationNodeTransformer
+import org.jetbrains.dokka.transformers.documentation.DocumentationToPageTranslator
+import org.jetbrains.dokka.transformers.pages.PageNodeTransformer
+import kotlin.reflect.KProperty
 
+
+/**
+ * Extension points declared by dokka core.
+ * Default values are stored in [org.jetbrains.dokka.plugability.DefaultExtensions]
+ */
 object CoreExtensions {
-    val nodeTransformer = ExtensionPoint<DocumentationNodeTransformer>(this::class.qualifiedName!!, "nodeTransformer")
-    val pageTransformer = ExtensionPoint<PageNodeTransformer>(this::class.qualifiedName!!, "pageTransformer")
-    val renderer = ExtensionPoint<Renderer>(this::class.qualifiedName!!, "renderer")
-    val locationProvider = ExtensionPoint<LocationProvider>(this::class.qualifiedName!!, "locationProvider")
+    val descriptorToDocumentationTranslator by coreExtension<DescriptorToDocumentationTranslator>()
+    val documentationMerger by coreExtension<DocumentationNodeMerger>()
+    val documentationTransformer by coreExtension<DocumentationNodeTransformer>()
+    val markdownToContentConverterFactory by coreExtension<(DokkaContext) -> MarkdownToContentConverter>()
+    val documentationToPageTranslator by coreExtension<DocumentationToPageTranslator>()
+    val pageTransformer by coreExtension<PageNodeTransformer>()
+    val renderer by coreExtension<(FileWriter, LocationProvider, DokkaContext) -> Renderer>()
+    val locationProvider by coreExtension<(root: PageNode, DokkaConfiguration, DokkaContext) -> LocationProvider>()
+    val fileExtension by coreExtension<String>()
+
+    private fun <T: Any> coreExtension() = object {
+        operator fun provideDelegate(thisRef: CoreExtensions, property: KProperty<*>): Lazy<ExtensionPoint<T>> =
+            lazy { ExtensionPoint<T>(thisRef::class.qualifiedName!!, property.name) }
+    }
 }
\ No newline at end of file
diff --git a/core/src/main/kotlin/DokkaDescriptorVisitor.kt b/core/src/main/kotlin/DokkaDescriptorVisitor.kt
deleted file mode 100644
index 6c82f2c3..00000000
--- a/core/src/main/kotlin/DokkaDescriptorVisitor.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.jetbrains.dokka
-
-import org.jetbrains.dokka.Model.*
-import org.jetbrains.dokka.Model.ClassKind
-import org.jetbrains.dokka.Model.Function
-import org.jetbrains.dokka.links.Callable
-import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.links.withClass
-import org.jetbrains.dokka.pages.PlatformData
-import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
-import org.jetbrains.kotlin.idea.kdoc.findKDoc
-import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
-import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
-import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
-import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
-import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
-import org.jetbrains.kotlin.resolve.scopes.MemberScope
-import org.jetbrains.kotlin.types.KotlinType
-
-class DokkaDescriptorVisitor(
-    private val platformData: PlatformData,
-    private val resolutionFacade: DokkaResolutionFacade
-) : DeclarationDescriptorVisitorEmptyBodies<DocumentationNode, DRI>() {
-    override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRI): Nothing {
-        throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}")
-    }
-
-    override fun visitPackageFragmentDescriptor(
-        descriptor: PackageFragmentDescriptor,
-        parent: DRI
-    ): Package {
-        val dri = DRI(packageName = descriptor.fqName.asString())
-        val scope = descriptor.getMemberScope()
-        return Package(
-            dri,
-            scope.functions(dri),
-            scope.properties(dri),
-            scope.classes(dri)
-        )
-    }
-
-    override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRI): Class {
-        val dri = parent.withClass(descriptor.name.asString())
-        val scope = descriptor.getMemberScope(emptyList())
-        val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData()
-        return Class(
-            dri,
-            descriptor.name.asString(),
-            KotlinClassKindTypes.valueOf(descriptor.kind.toString()),
-            descriptor.constructors.map { visitConstructorDescriptor(it, dri) },
-            scope.functions(dri),
-            scope.properties(dri),
-            scope.classes(dri),
-            descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(),
-            listOfNotNull(descriptorData),
-            getXMLDRIs(descriptor, descriptorData).toMutableSet()
-        )
-    }
-
-    override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRI): Property {
-        val dri = parent.copy(callable = Callable.from(descriptor))
-        return Property(
-            dri,
-            descriptor.name.asString(),
-            descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) },
-            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
-            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
-        )
-    }
-
-    override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRI): Function {
-        val dri = parent.copy(callable = Callable.from(descriptor))
-        return Function(
-            dri,
-            descriptor.name.asString(),
-            descriptor.returnType?.let { KotlinTypeWrapper(it) },
-            false,
-            descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) },
-            descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
-            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
-            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
-        )
-    }
-
-    override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRI): Function {
-        val dri = parent.copy(callable = Callable.from(descriptor))
-        return Function(
-            dri,
-            "<init>",
-            KotlinTypeWrapper(descriptor.returnType),
-            true,
-            null,
-            descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
-            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
-            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
-        )
-    }
-
-    override fun visitReceiverParameterDescriptor(
-        descriptor: ReceiverParameterDescriptor,
-        parent: DRI
-    ) = Parameter(
-        parent.copy(target = 0),
-        null,
-        KotlinTypeWrapper(descriptor.type),
-        listOf(descriptor.resolveDescriptorData())
-    )
-
-    private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRI) =
-        Parameter(
-            parent.copy(target = index + 1),
-            descriptor.name.asString(),
-            KotlinTypeWrapper(descriptor.type),
-            listOf(descriptor.resolveDescriptorData())
-        )
-
-    private fun MemberScope.functions(parent: DRI): List<Function> =
-        getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true }
-            .filterIsInstance<FunctionDescriptor>()
-            .map { visitFunctionDescriptor(it, parent) }
-
-    private fun MemberScope.properties(parent: DRI): List<Property> =
-        getContributedDescriptors(DescriptorKindFilter.VALUES) { true }
-            .filterIsInstance<PropertyDescriptor>()
-            .map { visitPropertyDescriptor(it, parent) }
-
-    private fun MemberScope.classes(parent: DRI): List<Class> =
-        getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true }
-            .filterIsInstance<ClassDescriptor>()
-            .map { visitClassDescriptor(it, parent) }
-
-    private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo {
-        val doc = findKDoc()
-        val links = doc?.children?.filter { it is KDocLink }?.flatMap { link ->
-            val destination = link.children.first { it is KDocName }.text
-            resolveKDocLink(
-                resolutionFacade.resolveSession.bindingContext,
-                resolutionFacade,
-                this,
-                null,
-                destination.split('.')
-            ).map { Pair(destination, DRI.from(it)) }
-        }?.toMap() ?: emptyMap()
-        return BasePlatformInfo(doc, links, listOf(platformData))
-    }
-
-    private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo {
-        return ClassPlatformInfo(resolveDescriptorData(),
-            (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) })
-    }
-
-    private fun getXMLDRIs(descriptor: DeclarationDescriptor, platformInfo: PlatformInfo?) =
-        platformInfo?.docTag?.children
-            ?.filter {
-                it.text.contains("@attr")
-            }?.flatMap { ref ->
-                val matchResult = "@attr\\s+ref\\s+(.+)".toRegex().matchEntire(ref.text)
-                val toFind = matchResult?.groups?.last()?.value.orEmpty()
-                resolveKDocLink(
-                    resolutionFacade.resolveSession.bindingContext,
-                    resolutionFacade,
-                    descriptor,
-                    null,
-                    toFind.split('.')
-                ).map { XMLMega("@attr ref", DRI.from(it)) }
-            }.orEmpty()
-}
-
-data class XMLMega(val key: String, val dri: DRI) : Extra
-
-enum class KotlinClassKindTypes : ClassKind {
-    CLASS,
-    INTERFACE,
-    ENUM_CLASS,
-    ENUM_ENTRY,
-    ANNOTATION_CLASS,
-    OBJECT;
-}
-
-class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper {
-    private val declarationDescriptor = kotlinType.constructor.declarationDescriptor
-    private val fqNameSafe = declarationDescriptor?.fqNameSafe
-    override val constructorFqName = fqNameSafe?.asString()
-    override val constructorNamePathSegments: List<String> =
-        fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList()
-    override val arguments: List<KotlinTypeWrapper> by lazy { kotlinType.arguments.map { KotlinTypeWrapper(it.type) } }
-    override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } }
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt
index 05eb3054..b6bf7a73 100644
--- a/core/src/main/kotlin/DokkaGenerator.kt
+++ b/core/src/main/kotlin/DokkaGenerator.kt
@@ -1,20 +1,24 @@
 package org.jetbrains.dokka
 
 import org.jetbrains.dokka.Model.Module
-import org.jetbrains.dokka.Model.transformers.DocumentationNodesMerger
 import org.jetbrains.dokka.Utilities.pretty
 import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.pages.MarkdownToContentConverter
+import org.jetbrains.dokka.pages.DefaultMarkdownToContentConverter
 import org.jetbrains.dokka.pages.PlatformData
 import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.single
 import org.jetbrains.dokka.renderers.FileWriter
 import org.jetbrains.dokka.renderers.HtmlRenderer
 import org.jetbrains.dokka.resolvers.DefaultLocationProvider
-import org.jetbrains.dokka.transformers.DefaultDocumentationToPageTransformer
+import org.jetbrains.dokka.resolvers.LocationProvider
+import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationToPageTranslator
+import org.jetbrains.dokka.transformers.descriptors.DokkaDescriptorVisitor
+import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationNodeMerger
 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.utils.PathUtil
 import java.io.File
 
@@ -22,56 +26,131 @@ class DokkaGenerator(
     private val configuration: DokkaConfiguration,
     private val logger: DokkaLogger
 ) {
-    fun generate(): Unit {
+
+    fun generate() {
+        logger.debug("Setting up analysis environments")
+        val platforms: Map<PlatformData, EnvironmentAndFacade> = configuration.passesConfigurations.map {
+            PlatformData(it.analysisPlatform, it.targets) to createEnvironmentAndFacade(it)
+        }.toMap()
 
         logger.debug("Initializing plugins")
-        val context = DokkaContext.create(configuration.pluginsClasspath, logger)
-
-        configuration.passesConfigurations.map { pass ->
-            AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run {
-                if (analysisPlatform == Platform.jvm) {
-                    addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
-                }
-                for (element in pass.classpath) {
-                    addClasspath(File(element))
-                }
-
-                addSources(pass.sourceRoots.map { it.path })
-
-                loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion)
-
-                val environment = createCoreEnvironment()
-                val (facade, _) = createResolutionFacade(environment)
-
-                environment.getSourceFiles().asSequence()
-                    .map { it.packageFqName }
-                    .distinct()
-                    .mapNotNull { facade.resolveSession.getPackageFragment(it) }
-                    .map {
-                        DokkaDescriptorVisitor(
-                            PlatformData(pass.analysisPlatform, pass.targets),
-                            facade
-                        ).visitPackageFragmentDescriptor(
-                            it,
-                            DRI.topLevel
-                        )
-                    }
-                    .toList()
-                    .let { Module(it) }
-                    .let { DocumentationNodesMerger(it) }
-                    .also { println("${pass.analysisPlatform}:\n${it.pretty()}\n\n") }
+        val context = DokkaContext.create(configuration.pluginsClasspath, logger, platforms)
+
+        logger.debug("Creating documentation models")
+        val modulesFromPlatforms = platforms.map { (pdata, _) -> translateDescriptors(pdata, context) }
+
+        logger.debug("Merging documentation models")
+        val documentationModel = context.single(CoreExtensions.documentationMerger)
+            .invoke(modulesFromPlatforms, context)
+
+        logger.debug("Transforming documentation model")
+        val transformedDocumentation = context[CoreExtensions.documentationTransformer]
+            .fold(documentationModel) { acc, t -> t(acc, context) }
+
+        logger.debug("Creating pages")
+        val pages = context.single(CoreExtensions.documentationToPageTranslator)
+            .invoke(transformedDocumentation, context)
+
+        logger.debug("Transforming pages")
+        val transformedPages = context[CoreExtensions.pageTransformer]
+            .fold(pages) { acc, t -> t(acc, context) }
+
+        logger.debug("Rendering")
+        val fileWriter = FileWriter(configuration.outputDir, "")
+        val locationProvider = context.single(CoreExtensions.locationProvider)
+            .invoke(transformedPages, configuration, context)
+        val renderer = context.single(CoreExtensions.renderer)
+            .invoke(fileWriter, locationProvider, context)
+
+        renderer.render(transformedPages)
+    }
+
+//    fun generate(int: Int) {
+//
+//        logger.debug("Initializing plugins")
+//        val context = DokkaContext.create(configuration.pluginsClasspath, logger, platforms)
+//
+//        configuration.passesConfigurations.map { pass ->
+//            AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run {
+//                if (analysisPlatform == Platform.jvm) {
+//                    addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
+//                }
+//                for (element in pass.classpath) {
+//                    addClasspath(File(element))
+//                }
+//
+//                addSources(pass.sourceRoots.map { it.path })
+//
+//                loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion)
+//
+//                val environment = createCoreEnvironment()
+//                val (facade, _) = createResolutionFacade(environment)
+//
+//                environment.getSourceFiles().asSequence()
+//                        .map { it.packageFqName }
+//                        .distinct()
+//                        .mapNotNull { facade.resolveSession.getPackageFragment(it) }
+//                        .map {
+//                        DokkaDescriptorVisitor(
+//                            PlatformData(
+//                                pass.analysisPlatform,
+//                                pass.targets
+//                            ), facade
+//                        )
+//                            .visitPackageFragmentDescriptor(it, DRI.topLevel)
+//                    }
+//                    .toList()
+//                    .let { Module(it) }
+//                    .let { DefaultDocumentationNodeMerger(it) }
+//                    .also { println("${pass.analysisPlatform}:\n${it.pretty()}\n\n") }
+//            }
+//        }.let {
+//            val markdownConverter = DefaultMarkdownToContentConverter(logger)
+//            DefaultDocumentationToPageTranslator(
+//                markdownConverter,
+//                logger
+//            ).transform(
+//                DefaultDocumentationNodeMerger(
+//                    it
+//                )
+//            )
+//        }.let {
+//            context[CoreExtensions.pageTransformer].fold(it) { pn, t -> t(pn, context) }
+//        }.also {
+//            HtmlRenderer(
+//                FileWriter(configuration.outputDir, ""),
+//                DefaultLocationProvider(it, configuration, ".${configuration.format}")
+//            ).render(it)
+//        }
+//    }
+
+    private fun createEnvironmentAndFacade(pass: DokkaConfiguration.PassConfiguration): EnvironmentAndFacade =
+        AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run {
+            if (analysisPlatform == Platform.jvm) {
+                addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
             }
-        }.let {
-            val markdownConverter = MarkdownToContentConverter(logger)
-            DefaultDocumentationToPageTransformer(markdownConverter, logger).transform(DocumentationNodesMerger(it))
-        }.let {
-            context[CoreExtensions.pageTransformer].fold(it) { pn, t -> t.action.invoke(pn, context) }
-        }.also {
-            HtmlRenderer(
-                FileWriter(configuration.outputDir, ""),
-                DefaultLocationProvider(it, configuration, ".${configuration.format}")
-            ).render(it)
+            pass.classpath.forEach { addClasspath(File(it)) }
+
+            addSources(pass.sourceRoots.map { it.path })
+
+            loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion)
+
+            val environment = createCoreEnvironment()
+            val (facade, _) = createResolutionFacade(environment)
+            EnvironmentAndFacade(environment, facade)
         }
+
+    private fun translateDescriptors(platformData: PlatformData, context: DokkaContext): Module {
+        val (environment, facade) = context.platforms.getValue(platformData)
+
+        val packageFragments = environment.getSourceFiles().asSequence()
+            .map { it.packageFqName }
+            .distinct()
+            .mapNotNull { facade.resolveSession.getPackageFragment(it) }
+            .toList()
+
+        return context.single(CoreExtensions.descriptorToDocumentationTranslator)
+            .invoke(packageFragments, platformData, context)
     }
 
     private class DokkaMessageCollector(private val logger: DokkaLogger) : MessageCollector {
@@ -90,4 +169,10 @@ class DokkaGenerator(
 
         override fun hasErrors() = seenErrors
     }
+}
+
+// It is not data class due to ill-defined equals
+class EnvironmentAndFacade(val environment: KotlinCoreEnvironment, val facade: DokkaResolutionFacade) {
+    operator fun component1() = environment
+    operator fun component2() = facade
 }
\ No newline at end of file
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index 0adb37b5..b1d4be55 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -1,6 +1,6 @@
 package org.jetbrains.dokka.Model
 
-import org.jetbrains.dokka.KotlinTypeWrapper
+import org.jetbrains.dokka.transformers.descriptors.KotlinTypeWrapper
 import org.jetbrains.dokka.links.DRI
 import org.jetbrains.dokka.pages.PlatformData
 import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
diff --git a/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt b/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt
deleted file mode 100644
index 318d20b3..00000000
--- a/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.jetbrains.dokka.Model.transformers
-
-import org.jetbrains.dokka.Model.Module
-
-interface DocumentationNodeTransformer {
-    operator fun invoke(original: Module): Module
-    operator fun invoke(modules: Collection<Module>): Module
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt b/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
deleted file mode 100644
index ae4f8d99..00000000
--- a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.jetbrains.dokka.Model.transformers
-
-import org.jetbrains.dokka.Model.*
-import org.jetbrains.dokka.Model.Function
-
-internal object DocumentationNodesMerger : DocumentationNodeTransformer {
-    override fun invoke(original: Module) = Module(
-        original.packages.map { mergePackageContent(it) }
-    )
-    override fun invoke(modules: Collection<Module>): Module =
-        Module(merge(modules.flatMap { it.packages }, Package::mergeWith))
-}
-
-private fun mergePackageContent(original: Package) = Package(
-    original.dri,
-    merge(original.functions, Function::mergeWith),
-    merge(original.properties, Property::mergeWith),
-    merge(original.classes, Class::mergeWith)
-)
-
-private fun <T: DocumentationNode> merge(elements: List<T>, reducer: (T, T) -> T): List<T> =
-    elements.groupingBy { it.dri }
-        .reduce { _, left, right -> reducer(left, right)}
-        .values.toList()
-
-fun PlatformInfo.mergeWith(other: PlatformInfo?) = BasePlatformInfo(
-    docTag,
-    links,
-    (platformData + (other?.platformData ?: emptyList())).distinct()
-)
-
-fun ClassPlatformInfo.mergeWith(other: ClassPlatformInfo?) = ClassPlatformInfo(
-    info.mergeWith(other?.info),
-    (inherited + (other?.inherited ?: emptyList())).distinct()
-)
-
-fun List<ClassPlatformInfo>.mergeClassPlatformInfo() : List<ClassPlatformInfo> =
-    groupingBy { it.docTag.toString() + it.links + it.inherited}.reduce {
-            _, left, right -> left.mergeWith(right)
-    }.values.toList()
-
-fun List<PlatformInfo>.merge() : List<PlatformInfo> =
-    groupingBy { it.docTag.toString() + it.links }.reduce {
-        _, left, right -> left.mergeWith(right)
-    }.values.toList()
-
-fun Function.mergeWith(other: Function) = Function(
-    dri,
-    name,
-    returnType,
-    isConstructor,
-    if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
-    merge(parameters + other.parameters, Parameter::mergeWith),
-    expected?.mergeWith(other.expected),
-    (actual + other.actual).merge()
-)
-
-fun Property.mergeWith(other: Property) = Property(
-    dri,
-    name,
-    if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
-    expected?.mergeWith(other.expected),
-    (actual + other.actual).merge()
-)
-
-fun Class.mergeWith(other: Class) = Class(
-    dri,
-    name,
-    kind,
-    merge(constructors + other.constructors, Function::mergeWith),
-    merge(functions + other.functions, Function::mergeWith),
-    merge(properties + other.properties, Property::mergeWith),
-    merge(classes + other.classes, Class::mergeWith),
-    expected?.mergeWith(other.expected),
-    (actual + other.actual).mergeClassPlatformInfo()
-)
-
-fun Parameter.mergeWith(other: Parameter) = Parameter(
-    dri,
-    name,
-    type,
-    (actual + other.actual).merge()
-)
-
-fun Package.mergeWith(other: Package) = Package(
-    dri,
-    merge(functions + other.functions, Function::mergeWith),
-    merge(properties + other.properties, Property::mergeWith),
-    merge(classes + other.classes, Class::mergeWith)
-)
\ No newline at end of file
diff --git a/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt b/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt
new file mode 100644
index 00000000..72b5ead2
--- /dev/null
+++ b/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt
@@ -0,0 +1,229 @@
+package org.jetbrains.dokka.pages
+
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.jetbrains.dokka.MarkdownNode
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.plugability.DokkaContext
+
+class DefaultMarkdownToContentConverter(
+    private val context: DokkaContext
+) : MarkdownToContentConverter {
+    override fun buildContent(
+        node: MarkdownNode,
+        dci: DCI,
+        platforms: Set<PlatformData>,
+        links: Map<String, DRI>,
+        styles: Set<Style>,
+        extras: Set<Extra>
+
+    ): List<ContentNode> {
+//    println(tree.toTestString())
+
+        fun buildChildren(node: MarkdownNode, newStyles: Set<Style> = emptySet(), newExtras: Set<Extra> = emptySet()) =
+            node.children.flatMap {
+                buildContent(it, dci, platforms, links, styles + newStyles, extras + newExtras)
+            }.coalesceText(platforms, styles + newStyles, extras + newExtras)
+
+        fun buildHeader(level: Int) =
+            ContentHeader(buildChildren(node), level, dci, platforms, styles)
+
+        return when (node.type) {
+            MarkdownElementTypes.ATX_1 -> listOf(buildHeader(1))
+            MarkdownElementTypes.ATX_2 -> listOf(buildHeader(2))
+            MarkdownElementTypes.ATX_3 -> listOf(buildHeader(3))
+            MarkdownElementTypes.ATX_4 -> listOf(buildHeader(4))
+            MarkdownElementTypes.ATX_5 -> listOf(buildHeader(5))
+            MarkdownElementTypes.ATX_6 -> listOf(buildHeader(6))
+            MarkdownElementTypes.UNORDERED_LIST -> listOf(
+                ContentList(
+                    buildChildren(node),
+                    false,
+                    dci,
+                    platforms,
+                    styles,
+                    extras
+                )
+            )
+            MarkdownElementTypes.ORDERED_LIST -> listOf(
+                ContentList(
+                    buildChildren(node),
+                    true,
+                    dci,
+                    platforms,
+                    styles,
+                    extras
+                )
+            )
+            MarkdownElementTypes.LIST_ITEM -> TODO()
+            MarkdownElementTypes.STRONG,
+            MarkdownTokenTypes.EMPH,
+            MarkdownElementTypes.EMPH ->
+                buildChildren(node, setOf(TextStyle.Strong))
+            // TODO
+            MarkdownElementTypes.CODE_SPAN -> TODO()
+//            val startDelimiter = node.child(MarkdownTokenTypes.BACKTICK)?.text
+//            if (startDelimiter != null) {
+//                val text = node.text.substring(startDelimiter.length).removeSuffix(startDelimiter)
+//                val codeSpan = ContentCode().apply { append(ContentText(text)) }
+//                parent.append(codeSpan)
+//            }
+
+            MarkdownElementTypes.CODE_BLOCK,
+            MarkdownElementTypes.CODE_FENCE -> {
+                val language = node.child(MarkdownTokenTypes.FENCE_LANG)?.text?.trim() ?: ""
+                listOf(ContentCode(buildChildren(node), language, dci, platforms, styles, extras)) // TODO
+            }
+            MarkdownElementTypes.PARAGRAPH -> buildChildren(node, newStyles = setOf(TextStyle.Paragraph))
+
+            MarkdownElementTypes.INLINE_LINK -> {
+//            val linkTextNode = node.child(MarkdownElementTypes.LINK_TEXT)
+//            val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
+//            if (linkTextNode != null) {
+//                if (destination != null) {
+//                    val link = ContentExternalLink(destination.text)
+//                    renderLinkTextTo(linkTextNode, link, linkResolver)
+//                    parent.append(link)
+//                } else {
+//                    val link = ContentExternalLink(linkTextNode.getLabelText())
+//                    renderLinkTextTo(linkTextNode, link, linkResolver)
+//                    parent.append(link)
+//                }
+//            }
+                //TODO: Linking!!!
+//            ContentLink()
+                TODO()
+            }
+            MarkdownElementTypes.SHORT_REFERENCE_LINK,
+            MarkdownElementTypes.FULL_REFERENCE_LINK -> {
+                val destinationNode = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION }
+                        ?: node.children.first { it.type == MarkdownElementTypes.LINK_LABEL }
+                val destination = destinationNode.children.find { it.type == MarkdownTokenTypes.TEXT }?.text
+                        ?: destinationNode.text
+                links[destination]?.let { dri ->
+                    listOf(
+                        ContentResolvedLink(
+                            buildChildren(node),
+                            destination,
+                            DCI(dri, ContentKind.Symbol),
+                            platforms,
+                            styles,
+                            extras
+                        )
+                    )
+                } ?: let {
+                    context.logger.error("Apparently there is no link resolved for $destination")
+                    emptyList<ContentNode>()
+                }
+            }
+            MarkdownTokenTypes.WHITE_SPACE -> {
+                // Don't append first space if start of header (it is added during formatting later)
+                //                   v
+                //               #### Some Heading
+//            if (nodeStack.peek() !is ContentHeading || node.parent?.children?.first() != node) {
+//                parent.append(ContentText(node.text))
+//            }
+                listOf(ContentText(" ", dci, platforms, styles, extras))
+            }
+            MarkdownTokenTypes.EOL -> {
+//            if ((keepEol(nodeStack.peek()) && node.parent?.children?.last() != node) ||
+//                // Keep extra blank lines when processing lists (affects Markdown formatting)
+//                (processingList(nodeStack.peek()) && node.previous?.type == MarkdownTokenTypes.EOL)) {
+//                parent.append(ContentText(node.text))
+//            }
+                listOf(ContentText(" ", dci, platforms, styles, extras))
+            }
+
+            MarkdownTokenTypes.CODE_LINE -> {
+                listOf(ContentText(node.text, dci, platforms, styles, extras)) // TODO check
+//            if (parent is ContentBlockCode) {
+//                parent.append(content)
+//            } else {
+//                parent.append(ContentBlockCode().apply { append(content) })
+//            }
+            }
+
+            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))
+                listOf(ContentText(node.text, dci, platforms, styles, extras)) // TODO
+
+            MarkdownTokenTypes.COLON,
+            MarkdownTokenTypes.SINGLE_QUOTE,
+            MarkdownTokenTypes.DOUBLE_QUOTE,
+            MarkdownTokenTypes.LT,
+            MarkdownTokenTypes.GT,
+            MarkdownTokenTypes.LPAREN,
+            MarkdownTokenTypes.RPAREN,
+            MarkdownTokenTypes.LBRACKET,
+            MarkdownTokenTypes.RBRACKET,
+            MarkdownTokenTypes.EXCLAMATION_MARK,
+            MarkdownTokenTypes.BACKTICK,
+            MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
+                listOf(ContentText(node.text, dci, platforms, styles, extras))
+            }
+
+            MarkdownElementTypes.LINK_DEFINITION -> TODO()
+
+            MarkdownTokenTypes.EMAIL_AUTOLINK ->
+                listOf(
+                    ContentResolvedLink(
+                        listOf(ContentText(node.text, dci, platforms, styles, extras)),
+                        "mailto:${node.text}",
+                        dci, platforms, styles, extras
+                    )
+                )
+
+            else -> buildChildren(node)
+        }
+    }
+
+    private fun Collection<ContentNode>.coalesceText(
+        platforms: Set<PlatformData>,
+        styles: Set<Style>,
+        extras: Set<Extra>
+    ) =
+        this
+            .sliceWhen { prev, next -> prev::class != next::class }
+            .flatMap { nodes ->
+                when (nodes.first()) {
+                    is ContentText -> listOf(
+                        ContentText(
+                            nodes.joinToString("") { (it as ContentText).text },
+                            nodes.first().dci, platforms, styles, extras
+                        )
+                    )
+                    else -> nodes
+                }
+            }
+}
+
+fun <T> Collection<T>.sliceWhen(predicate: (before: T, after: T) -> Boolean): Collection<Collection<T>> {
+    val newCollection = mutableListOf<Collection<T>>()
+    var currentSlice = mutableListOf<T>()
+    for ((prev, next) in this.windowed(2, 1, false)) {
+        currentSlice.add(prev)
+        if (predicate(prev, next)) {
+            newCollection.add(currentSlice)
+            currentSlice = mutableListOf<T>()
+        }
+    }
+    if (this.isNotEmpty()) {
+        currentSlice.add(this.last())
+        newCollection.add(currentSlice)
+    }
+    return newCollection
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/pages/MarkdownToContentConverter.kt b/core/src/main/kotlin/pages/MarkdownToContentConverter.kt
index 8caddeb0..fe4736b7 100644
--- a/core/src/main/kotlin/pages/MarkdownToContentConverter.kt
+++ b/core/src/main/kotlin/pages/MarkdownToContentConverter.kt
@@ -1,14 +1,10 @@
 package org.jetbrains.dokka.pages
 
-import org.intellij.markdown.MarkdownElementTypes
-import org.intellij.markdown.MarkdownTokenTypes
-import org.jetbrains.dokka.DokkaLogger
 import org.jetbrains.dokka.MarkdownNode
 import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.plugability.DokkaContext
 
-class MarkdownToContentConverter(
-    private val logger: DokkaLogger
-) {
+interface MarkdownToContentConverter {
     fun buildContent(
         node: MarkdownNode,
         dci: DCI,
@@ -16,214 +12,5 @@ class MarkdownToContentConverter(
         links: Map<String, DRI> = emptyMap(),
         styles: Set<Style> = emptySet(),
         extras: Set<Extra> = emptySet()
-
-    ): List<ContentNode> {
-//    println(tree.toTestString())
-
-        fun buildChildren(node: MarkdownNode, newStyles: Set<Style> = emptySet(), newExtras: Set<Extra> = emptySet()) =
-            node.children.flatMap {
-                buildContent(it, dci, platforms, links, styles + newStyles, extras + newExtras)
-            }.coalesceText(platforms, styles + newStyles, extras + newExtras)
-
-        fun buildHeader(level: Int) =
-            ContentHeader(buildChildren(node), level, dci, platforms, styles)
-
-        return when (node.type) {
-            MarkdownElementTypes.ATX_1 -> listOf(buildHeader(1))
-            MarkdownElementTypes.ATX_2 -> listOf(buildHeader(2))
-            MarkdownElementTypes.ATX_3 -> listOf(buildHeader(3))
-            MarkdownElementTypes.ATX_4 -> listOf(buildHeader(4))
-            MarkdownElementTypes.ATX_5 -> listOf(buildHeader(5))
-            MarkdownElementTypes.ATX_6 -> listOf(buildHeader(6))
-            MarkdownElementTypes.UNORDERED_LIST -> listOf(
-                ContentList(
-                    buildChildren(node),
-                    false,
-                    dci,
-                    platforms,
-                    styles,
-                    extras
-                )
-            )
-            MarkdownElementTypes.ORDERED_LIST -> listOf(
-                ContentList(
-                    buildChildren(node),
-                    true,
-                    dci,
-                    platforms,
-                    styles,
-                    extras
-                )
-            )
-            MarkdownElementTypes.LIST_ITEM -> TODO()
-            MarkdownElementTypes.STRONG,
-            MarkdownTokenTypes.EMPH,
-            MarkdownElementTypes.EMPH ->
-                buildChildren(node, setOf(TextStyle.Strong))
-            // TODO
-            MarkdownElementTypes.CODE_SPAN -> TODO()
-//            val startDelimiter = node.child(MarkdownTokenTypes.BACKTICK)?.text
-//            if (startDelimiter != null) {
-//                val text = node.text.substring(startDelimiter.length).removeSuffix(startDelimiter)
-//                val codeSpan = ContentCode().apply { append(ContentText(text)) }
-//                parent.append(codeSpan)
-//            }
-
-            MarkdownElementTypes.CODE_BLOCK,
-            MarkdownElementTypes.CODE_FENCE -> {
-                val language = node.child(MarkdownTokenTypes.FENCE_LANG)?.text?.trim() ?: ""
-                listOf(ContentCode(buildChildren(node), language, dci, platforms, styles, extras)) // TODO
-            }
-            MarkdownElementTypes.PARAGRAPH -> buildChildren(node, newStyles = setOf(TextStyle.Paragraph))
-
-            MarkdownElementTypes.INLINE_LINK -> {
-//            val linkTextNode = node.child(MarkdownElementTypes.LINK_TEXT)
-//            val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
-//            if (linkTextNode != null) {
-//                if (destination != null) {
-//                    val link = ContentExternalLink(destination.text)
-//                    renderLinkTextTo(linkTextNode, link, linkResolver)
-//                    parent.append(link)
-//                } else {
-//                    val link = ContentExternalLink(linkTextNode.getLabelText())
-//                    renderLinkTextTo(linkTextNode, link, linkResolver)
-//                    parent.append(link)
-//                }
-//            }
-                //TODO: Linking!!!
-//            ContentLink()
-                TODO()
-            }
-            MarkdownElementTypes.SHORT_REFERENCE_LINK,
-            MarkdownElementTypes.FULL_REFERENCE_LINK -> {
-                val destinationNode = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION }
-                        ?: node.children.first { it.type == MarkdownElementTypes.LINK_LABEL }
-                val destination = destinationNode.children.find { it.type == MarkdownTokenTypes.TEXT }?.text
-                        ?: destinationNode.text
-                links[destination]?.let { dri ->
-                    listOf(
-                        ContentResolvedLink(
-                            buildChildren(node),
-                            destination,
-                            DCI(dri, ContentKind.Symbol),
-                            platforms,
-                            styles,
-                            extras
-                        )
-                    )
-                } ?: let {
-                    logger.error("Apparently there is no link resolved for $destination")
-                    emptyList<ContentNode>()
-                }
-            }
-            MarkdownTokenTypes.WHITE_SPACE -> {
-                // Don't append first space if start of header (it is added during formatting later)
-                //                   v
-                //               #### Some Heading
-//            if (nodeStack.peek() !is ContentHeading || node.parent?.children?.first() != node) {
-//                parent.append(ContentText(node.text))
-//            }
-                listOf(ContentText(" ", dci, platforms, styles, extras))
-            }
-            MarkdownTokenTypes.EOL -> {
-//            if ((keepEol(nodeStack.peek()) && node.parent?.children?.last() != node) ||
-//                // Keep extra blank lines when processing lists (affects Markdown formatting)
-//                (processingList(nodeStack.peek()) && node.previous?.type == MarkdownTokenTypes.EOL)) {
-//                parent.append(ContentText(node.text))
-//            }
-                listOf(ContentText(" ", dci, platforms, styles, extras))
-            }
-
-            MarkdownTokenTypes.CODE_LINE -> {
-                listOf(ContentText(node.text, dci, platforms, styles, extras)) // TODO check
-//            if (parent is ContentBlockCode) {
-//                parent.append(content)
-//            } else {
-//                parent.append(ContentBlockCode().apply { append(content) })
-//            }
-            }
-
-            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))
-                listOf(ContentText(node.text, dci, platforms, styles, extras)) // TODO
-
-            MarkdownTokenTypes.COLON,
-            MarkdownTokenTypes.SINGLE_QUOTE,
-            MarkdownTokenTypes.DOUBLE_QUOTE,
-            MarkdownTokenTypes.LT,
-            MarkdownTokenTypes.GT,
-            MarkdownTokenTypes.LPAREN,
-            MarkdownTokenTypes.RPAREN,
-            MarkdownTokenTypes.LBRACKET,
-            MarkdownTokenTypes.RBRACKET,
-            MarkdownTokenTypes.EXCLAMATION_MARK,
-            MarkdownTokenTypes.BACKTICK,
-            MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
-                listOf(ContentText(node.text, dci, platforms, styles, extras))
-            }
-
-            MarkdownElementTypes.LINK_DEFINITION -> TODO()
-
-            MarkdownTokenTypes.EMAIL_AUTOLINK ->
-                listOf(
-                    ContentResolvedLink(
-                        listOf(ContentText(node.text, dci, platforms, styles, extras)),
-                        "mailto:${node.text}",
-                        dci, platforms, styles, extras
-                    )
-                )
-
-            else -> buildChildren(node)
-        }
-    }
-
-    private fun Collection<ContentNode>.coalesceText(
-        platforms: Set<PlatformData>,
-        styles: Set<Style>,
-        extras: Set<Extra>
-    ) =
-        this
-            .sliceWhen { prev, next -> prev::class != next::class }
-            .flatMap { nodes ->
-                when (nodes.first()) {
-                    is ContentText -> listOf(
-                        ContentText(
-                            nodes.joinToString("") { (it as ContentText).text },
-                            nodes.first().dci, platforms, styles, extras
-                        )
-                    )
-                    else -> nodes
-                }
-            }
+    ): List<ContentNode>
 }
-
-fun <T> Collection<T>.sliceWhen(predicate: (before: T, after: T) -> Boolean): Collection<Collection<T>> {
-    val newCollection = mutableListOf<Collection<T>>()
-    var currentSlice = mutableListOf<T>()
-    for ((prev, next) in this.windowed(2, 1, false)) {
-        currentSlice.add(prev)
-        if (predicate(prev, next)) {
-            newCollection.add(currentSlice)
-            currentSlice = mutableListOf<T>()
-        }
-    }
-    if (this.isNotEmpty()) {
-        currentSlice.add(this.last())
-        newCollection.add(currentSlice)
-    }
-    return newCollection
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/pages/transformers/PageNodeTransformer.kt b/core/src/main/kotlin/pages/transformers/PageNodeTransformer.kt
deleted file mode 100644
index 0d0f8057..00000000
--- a/core/src/main/kotlin/pages/transformers/PageNodeTransformer.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.jetbrains.dokka.pages.transformers
-
-import org.jetbrains.dokka.pages.ModulePageNode
-
-interface PageNodeTransformer {
-    operator fun invoke(original: ModulePageNode): ModulePageNode
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/plugability/DefaultExtensions.kt b/core/src/main/kotlin/plugability/DefaultExtensions.kt
new file mode 100644
index 00000000..dd656386
--- /dev/null
+++ b/core/src/main/kotlin/plugability/DefaultExtensions.kt
@@ -0,0 +1,25 @@
+package org.jetbrains.dokka.plugability
+
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.pages.DefaultMarkdownToContentConverter
+import org.jetbrains.dokka.renderers.DefaultRenderer
+import org.jetbrains.dokka.renderers.HtmlRenderer
+import org.jetbrains.dokka.resolvers.DefaultLocationProvider
+import org.jetbrains.dokka.transformers.descriptors.DefaultDescriptorToDocumentationTranslator
+import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationNodeMerger
+import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationToPageTranslator
+
+object DefaultExtensions : DokkaExtensionHandler {
+    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
+    override fun <T : Any, E : ExtensionPoint<T>> get(point: E, askDefault: AskDefault): List<T> =
+        when (point) {
+            CoreExtensions.descriptorToDocumentationTranslator -> DefaultDescriptorToDocumentationTranslator
+            CoreExtensions.documentationMerger -> DefaultDocumentationNodeMerger
+            CoreExtensions.markdownToContentConverterFactory -> ::DefaultMarkdownToContentConverter
+            CoreExtensions.documentationToPageTranslator -> DefaultDocumentationToPageTranslator
+            CoreExtensions.renderer -> ::HtmlRenderer
+            CoreExtensions.locationProvider -> ::DefaultLocationProvider
+            CoreExtensions.fileExtension -> ".html"
+            else -> null
+        }.let { listOfNotNull(it) as List<T> }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt
index b917ff99..31c56728 100644
--- a/core/src/main/kotlin/plugability/DokkaContext.kt
+++ b/core/src/main/kotlin/plugability/DokkaContext.kt
@@ -1,23 +1,35 @@
 package org.jetbrains.dokka.plugability
 
 import org.jetbrains.dokka.DokkaLogger
+import org.jetbrains.dokka.EnvironmentAndFacade
+import org.jetbrains.dokka.pages.PlatformData
 import java.io.File
 import java.net.URLClassLoader
 import java.util.*
 import kotlin.reflect.KClass
 import kotlin.reflect.full.createInstance
 
-interface DokkaContext {
-    operator fun <T, E> get(point: E, askDefault: AskDefault = AskDefault.WhenEmpty): List<Extension<T>>
+
+interface DokkaExtensionHandler {
+    operator fun <T, E> get(point: E, askDefault: AskDefault = AskDefault.WhenEmpty): List<T>
             where T : Any, E : ExtensionPoint<T>
 
+}
+
+interface DokkaContext : DokkaExtensionHandler {
     fun <T : DokkaPlugin> plugin(kclass: KClass<T>): T?
 
     val logger: DokkaLogger
 
+    val platforms: Map<PlatformData, EnvironmentAndFacade>
+
     companion object {
-        fun create(pluginsClasspath: Iterable<File>, logger: DokkaLogger): DokkaContext =
-            DokkaContextConfigurationImpl(logger, DefaultContext(logger)).apply {
+        fun create(
+            pluginsClasspath: Iterable<File>,
+            logger: DokkaLogger,
+            platforms: Map<PlatformData, EnvironmentAndFacade>
+        ): DokkaContext =
+            DokkaContextConfigurationImpl(logger, DefaultExtensions, platforms).apply {
                 pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() }
                     .toTypedArray()
                     .let { URLClassLoader(it, this.javaClass.classLoader) }
@@ -28,13 +40,27 @@ interface DokkaContext {
     }
 }
 
+fun <T, E> DokkaContext.single(point: E): T where T : Any, E : ExtensionPoint<T> {
+    fun throwBadArity(substitution: String): Nothing = throw IllegalStateException(
+        "$point was expected to have exactly one extension registered, but $substitution found."
+    )
+
+    val extensions = get(point, AskDefault.WhenEmpty)
+    return when (extensions.size) {
+        0 -> throwBadArity("none was")
+        1 -> extensions.first()
+        else -> throwBadArity("multiple were")
+    }
+}
+
 interface DokkaContextConfiguration {
     fun addExtension(extension: Extension<*>)
 }
 
 private class DokkaContextConfigurationImpl(
     override val logger: DokkaLogger,
-    private val defaultContext: DokkaContext?
+    private val defaultHandler: DokkaExtensionHandler?,
+    override val platforms: Map<PlatformData, EnvironmentAndFacade>
 ) : DokkaContext, DokkaContextConfiguration {
     private val plugins = mutableMapOf<KClass<*>, DokkaPlugin>()
 
@@ -45,15 +71,16 @@ private class DokkaContextConfigurationImpl(
     @Suppress("UNCHECKED_CAST")
     override operator fun <T, E> get(point: E, askDefault: AskDefault) where T : Any, E : ExtensionPoint<T> =
         when (askDefault) {
-            AskDefault.Never -> extensions[point].orEmpty()
-            AskDefault.Always -> extensions[point].orEmpty() + defaultContext?.get(point, askDefault).orEmpty()
+            AskDefault.Never -> actions(point).orEmpty()
+            AskDefault.Always -> actions(point).orEmpty() + defaultHandler?.get(point, askDefault).orEmpty()
             AskDefault.WhenEmpty ->
-                extensions[point]?.takeIf { it.isNotEmpty() } ?: defaultContext?.get(point, askDefault).orEmpty()
-        } as List<Extension<T>>
+                actions(point)?.takeIf { it.isNotEmpty() } ?: defaultHandler?.get(point, askDefault).orEmpty()
+        } as List<T>
+
+    private fun <E : ExtensionPoint<*>> actions(point: E) = extensions[point]?.map { it.action }
 
     @Suppress("UNCHECKED_CAST")
-    override fun <T : DokkaPlugin> plugin(kclass: KClass<T>) =
-        (plugins[kclass] ?: defaultContext?.plugin(kclass) ?: pluginStubFor(kclass)) as T
+    override fun <T : DokkaPlugin> plugin(kclass: KClass<T>) = (plugins[kclass] ?: pluginStubFor(kclass)) as T
 
     private fun <T : DokkaPlugin> pluginStubFor(kclass: KClass<T>): DokkaPlugin =
         pluginStubs.getOrPut(kclass) { kclass.createInstance().also { it.context = this } }
@@ -69,7 +96,7 @@ private class DokkaContextConfigurationImpl(
     }
 
     fun logInitialisationInfo() {
-        val pluginNames: List<String> = plugins.values.map { it::class.qualifiedName.toString() }
+        val pluginNames = plugins.values.map { it::class.qualifiedName.toString() }
 
         val loadedListForDebug = extensions.run { keys + values.flatten() }.toList()
             .joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" }
@@ -89,16 +116,6 @@ private fun checkClasspath(classLoader: URLClassLoader) {
     }
 }
 
-class DefaultContext(override val logger: DokkaLogger) : DokkaContext {
-    override fun <T : Any, E : ExtensionPoint<T>> get(point: E, askDefault: AskDefault): List<Extension<T>> =
-        when (point) {
-
-            else -> emptyList()
-        }
-
-    override fun <T : DokkaPlugin> plugin(kclass: KClass<T>): Nothing? = null
-}
-
 enum class AskDefault {
     Always, Never, WhenEmpty
 }
\ No newline at end of file
diff --git a/core/src/main/kotlin/plugability/extensions.kt b/core/src/main/kotlin/plugability/extensions.kt
index c1573e1a..3039cb5a 100644
--- a/core/src/main/kotlin/plugability/extensions.kt
+++ b/core/src/main/kotlin/plugability/extensions.kt
@@ -11,7 +11,7 @@ class Extension<T : Any> internal constructor(
     internal val extensionPoint: ExtensionPoint<T>,
     internal val pluginClass: String,
     internal val extensionName: String,
-    internal val action: T,
+    val action: T,
     internal val ordering: (OrderDsl.() -> Unit)? = null
 ) {
     override fun toString() = "Extension: $pluginClass/$extensionName"
diff --git a/core/src/main/kotlin/renderers/DefaultRenderer.kt b/core/src/main/kotlin/renderers/DefaultRenderer.kt
index 3b16c093..3f851849 100644
--- a/core/src/main/kotlin/renderers/DefaultRenderer.kt
+++ b/core/src/main/kotlin/renderers/DefaultRenderer.kt
@@ -1,9 +1,14 @@
 package org.jetbrains.dokka.renderers
 
 import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
 import org.jetbrains.dokka.resolvers.LocationProvider
 
-abstract class DefaultRenderer(val fileWriter: FileWriter, val locationProvider: LocationProvider) : Renderer {
+abstract class DefaultRenderer(
+    protected val fileWriter: FileWriter,
+    protected val locationProvider: LocationProvider,
+    protected val context: DokkaContext
+) : Renderer {
 
     protected abstract fun buildHeader(level: Int, text: String): String
     protected abstract fun buildLink(text: String, address: String): String
diff --git a/core/src/main/kotlin/renderers/HtmlRenderer.kt b/core/src/main/kotlin/renderers/HtmlRenderer.kt
index 3b778671..46548699 100644
--- a/core/src/main/kotlin/renderers/HtmlRenderer.kt
+++ b/core/src/main/kotlin/renderers/HtmlRenderer.kt
@@ -2,13 +2,18 @@ package org.jetbrains.dokka.renderers
 
 import org.jetbrains.dokka.htmlEscape
 import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
 import org.jetbrains.dokka.resolvers.LocationProvider
 import java.io.File
 import java.net.URL
 import java.nio.file.Path
 import java.nio.file.Paths
 
-open class HtmlRenderer(fileWriter: FileWriter, locationProvider: LocationProvider) : DefaultRenderer(fileWriter, locationProvider) {
+open class HtmlRenderer(
+    fileWriter: FileWriter,
+    locationProvider: LocationProvider,
+    context: DokkaContext
+) : DefaultRenderer(fileWriter, locationProvider, context) {
 
     override fun buildList(node: ContentList, pageContext: PageNode): String = if (node.ordered) {
         "<ol>${buildListItems(node.children, pageContext)}</ol>"
diff --git a/core/src/main/kotlin/renderers/Renderer.kt b/core/src/main/kotlin/renderers/Renderer.kt
index 24e01cdb..91483a46 100644
--- a/core/src/main/kotlin/renderers/Renderer.kt
+++ b/core/src/main/kotlin/renderers/Renderer.kt
@@ -1,6 +1,7 @@
 package org.jetbrains.dokka.renderers
 
 import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.plugability.DokkaContext
 
 interface Renderer {
     fun render(root: PageNode)
diff --git a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt
index 208410fe..ec5aa29a 100644
--- a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt
+++ b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt
@@ -1,11 +1,20 @@
 package org.jetbrains.dokka.resolvers
 
+import org.jetbrains.dokka.CoreExtensions
 import org.jetbrains.dokka.DokkaConfiguration
 import org.jetbrains.dokka.htmlEscape
 import org.jetbrains.dokka.links.DRI
 import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.single
+
+open class DefaultLocationProvider(
+    private val pageGraphRoot: PageNode,
+    private val configuration: DokkaConfiguration,
+    context: DokkaContext
+): LocationProvider { // TODO: cache
+    private val extension = context.single(CoreExtensions.fileExtension)
 
-open class DefaultLocationProvider(private val pageGraphRoot: PageNode, val configuration: DokkaConfiguration, val extension: String): LocationProvider { // TODO: cache
     override fun resolve(node: PageNode, context: PageNode?): String = pathTo(node, context) + extension
 
     override fun resolve(dri: DRI, platforms: List<PlatformData>, context: PageNode?): String =
diff --git a/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt b/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
deleted file mode 100644
index b0877527..00000000
--- a/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.jetbrains.dokka.transformers
-
-import org.jetbrains.dokka.DokkaLogger
-import org.jetbrains.dokka.Model.Module
-import org.jetbrains.dokka.pages.DefaultPageBuilder
-import org.jetbrains.dokka.pages.DefaultPageContentBuilder
-import org.jetbrains.dokka.pages.MarkdownToContentConverter
-import org.jetbrains.dokka.pages.ModulePageNode
-
-
-class DefaultDocumentationToPageTransformer(
-    private val markdownConverter: MarkdownToContentConverter,
-    private val logger: DokkaLogger
-) : DocumentationToPageTransformer {
-    override fun transform(module: Module): ModulePageNode =
-        DefaultPageBuilder { node, kind, operation ->
-            DefaultPageContentBuilder.group(node.dri, node.platformData, kind, markdownConverter, logger, operation)
-        }.pageForModule(module)
-
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/DocumentationToPageTransformer.kt b/core/src/main/kotlin/transformers/DocumentationToPageTransformer.kt
deleted file mode 100644
index 19703025..00000000
--- a/core/src/main/kotlin/transformers/DocumentationToPageTransformer.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.jetbrains.dokka.transformers
-
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.Model.DocumentationNode
-import org.jetbrains.dokka.Model.Module
-import org.jetbrains.dokka.pages.ModulePageNode
-import org.jetbrains.dokka.pages.PageNode
-
-interface DocumentationToPageTransformer {
-    fun transform(module: Module): ModulePageNode // TODO refactor this... some more?
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/PageNodeTransformer.kt b/core/src/main/kotlin/transformers/PageNodeTransformer.kt
deleted file mode 100644
index b86e0b34..00000000
--- a/core/src/main/kotlin/transformers/PageNodeTransformer.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.jetbrains.dokka.transformers
-
-import org.jetbrains.dokka.pages.ModulePageNode
-import org.jetbrains.dokka.plugability.DokkaContext
-
-interface PageNodeTransformer {
-    fun invoke(input: ModulePageNode, dokkaContext: DokkaContext): ModulePageNode
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt
new file mode 100644
index 00000000..19aeef92
--- /dev/null
+++ b/core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt
@@ -0,0 +1,208 @@
+package org.jetbrains.dokka.transformers.descriptors
+
+import org.jetbrains.dokka.DokkaResolutionFacade
+import org.jetbrains.dokka.Model.*
+import org.jetbrains.dokka.Model.ClassKind
+import org.jetbrains.dokka.Model.Function
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.withClass
+import org.jetbrains.dokka.pages.PlatformData
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
+import org.jetbrains.kotlin.idea.kdoc.findKDoc
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
+import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
+import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.scopes.MemberScope
+import org.jetbrains.kotlin.types.KotlinType
+
+object DefaultDescriptorToDocumentationTranslator: DescriptorToDocumentationTranslator {
+    override fun invoke(
+        packageFragments: Iterable<PackageFragmentDescriptor>,
+        platformData: PlatformData,
+        context: DokkaContext
+    ) = DokkaDescriptorVisitor(platformData, context.platforms[platformData]?.facade!!).run {
+        packageFragments.map { visitPackageFragmentDescriptor(it, DRI.topLevel) }
+    }.let { Module(it) }
+
+}
+
+class DokkaDescriptorVisitor(
+    private val platformData: PlatformData,
+    private val resolutionFacade: DokkaResolutionFacade
+) : DeclarationDescriptorVisitorEmptyBodies<DocumentationNode, DRI>() {
+    override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRI): Nothing {
+        throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}")
+    }
+
+    override fun visitPackageFragmentDescriptor(
+        descriptor: PackageFragmentDescriptor,
+        parent: DRI
+    ): Package {
+        val dri = DRI(packageName = descriptor.fqName.asString())
+        val scope = descriptor.getMemberScope()
+        return Package(
+            dri,
+            scope.functions(dri),
+            scope.properties(dri),
+            scope.classes(dri)
+        )
+    }
+
+    override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRI): Class {
+        val dri = parent.withClass(descriptor.name.asString())
+        val scope = descriptor.getMemberScope(emptyList())
+        val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData()
+        return Class(
+            dri,
+            descriptor.name.asString(),
+            KotlinClassKindTypes.valueOf(descriptor.kind.toString()),
+            descriptor.constructors.map { visitConstructorDescriptor(it, dri) },
+            scope.functions(dri),
+            scope.properties(dri),
+            scope.classes(dri),
+            descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(),
+            listOfNotNull(descriptorData),
+            getXMLDRIs(descriptor, descriptorData).toMutableSet()
+        )
+    }
+
+    override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRI): Property {
+        val dri = parent.copy(callable = Callable.from(descriptor))
+        return Property(
+            dri,
+            descriptor.name.asString(),
+            descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) },
+            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
+            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
+        )
+    }
+
+    override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRI): Function {
+        val dri = parent.copy(callable = Callable.from(descriptor))
+        return Function(
+            dri,
+            descriptor.name.asString(),
+            descriptor.returnType?.let { KotlinTypeWrapper(it) },
+            false,
+            descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) },
+            descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
+            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
+            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
+        )
+    }
+
+    override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRI): Function {
+        val dri = parent.copy(callable = Callable.from(descriptor))
+        return Function(
+            dri,
+            "<init>",
+            KotlinTypeWrapper(descriptor.returnType),
+            true,
+            null,
+            descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
+            descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
+            listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())
+        )
+    }
+
+    override fun visitReceiverParameterDescriptor(
+        descriptor: ReceiverParameterDescriptor,
+        parent: DRI
+    ) = Parameter(
+        parent.copy(target = 0),
+        null,
+        KotlinTypeWrapper(descriptor.type),
+        listOf(descriptor.resolveDescriptorData())
+    )
+
+    private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRI) =
+        Parameter(
+            parent.copy(target = index + 1),
+            descriptor.name.asString(),
+            KotlinTypeWrapper(descriptor.type),
+            listOf(descriptor.resolveDescriptorData())
+        )
+
+    private fun MemberScope.functions(parent: DRI): List<Function> =
+        getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true }
+            .filterIsInstance<FunctionDescriptor>()
+            .map { visitFunctionDescriptor(it, parent) }
+
+    private fun MemberScope.properties(parent: DRI): List<Property> =
+        getContributedDescriptors(DescriptorKindFilter.VALUES) { true }
+            .filterIsInstance<PropertyDescriptor>()
+            .map { visitPropertyDescriptor(it, parent) }
+
+    private fun MemberScope.classes(parent: DRI): List<Class> =
+        getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true }
+            .filterIsInstance<ClassDescriptor>()
+            .map { visitClassDescriptor(it, parent) }
+
+    private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo {
+        val doc = findKDoc()
+        val links = doc?.children?.filter { it is KDocLink }?.flatMap { link ->
+            val destination = link.children.first { it is KDocName }.text
+            resolveKDocLink(
+                resolutionFacade.resolveSession.bindingContext,
+                resolutionFacade,
+                this,
+                null,
+                destination.split('.')
+            ).map { Pair(destination, DRI.from(it)) }
+        }?.toMap() ?: emptyMap()
+        return BasePlatformInfo(doc, links, listOf(platformData))
+    }
+
+    private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo {
+        return ClassPlatformInfo(resolveDescriptorData(),
+            (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) })
+    }
+
+    private fun getXMLDRIs(descriptor: DeclarationDescriptor, platformInfo: PlatformInfo?) =
+        platformInfo?.docTag?.children
+            ?.filter {
+                it.text.contains("@attr")
+            }?.flatMap { ref ->
+                val matchResult = "@attr\\s+ref\\s+(.+)".toRegex().matchEntire(ref.text)
+                val toFind = matchResult?.groups?.last()?.value.orEmpty()
+                resolveKDocLink(
+                    resolutionFacade.resolveSession.bindingContext,
+                    resolutionFacade,
+                    descriptor,
+                    null,
+                    toFind.split('.')
+                ).map { XMLMega("@attr ref", DRI.from(it)) }
+            }.orEmpty()
+}
+
+data class XMLMega(val key: String, val dri: DRI) : Extra
+
+enum class KotlinClassKindTypes : ClassKind {
+    CLASS,
+    INTERFACE,
+    ENUM_CLASS,
+    ENUM_ENTRY,
+    ANNOTATION_CLASS,
+    OBJECT;
+}
+
+class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper {
+    private val declarationDescriptor = kotlinType.constructor.declarationDescriptor
+    private val fqNameSafe = declarationDescriptor?.fqNameSafe
+    override val constructorFqName = fqNameSafe?.asString()
+    override val constructorNamePathSegments: List<String> =
+        fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList()
+    override val arguments: List<KotlinTypeWrapper> by lazy { kotlinType.arguments.map {
+        KotlinTypeWrapper(
+            it.type
+        )
+    } }
+    override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt b/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt
new file mode 100644
index 00000000..d08aba21
--- /dev/null
+++ b/core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt
@@ -0,0 +1,15 @@
+package org.jetbrains.dokka.transformers.descriptors
+
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.Model.Package
+import org.jetbrains.dokka.pages.PlatformData
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
+
+interface DescriptorToDocumentationTranslator {
+    fun invoke(
+        packageFragments: Iterable<PackageFragmentDescriptor>,
+        platformData: PlatformData,
+        context: DokkaContext
+    ): Module
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt
new file mode 100644
index 00000000..46ba2816
--- /dev/null
+++ b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt
@@ -0,0 +1,86 @@
+package org.jetbrains.dokka.transformers.documentation
+
+import org.jetbrains.dokka.Model.*
+import org.jetbrains.dokka.Model.Function
+import org.jetbrains.dokka.plugability.DokkaContext
+
+internal object DefaultDocumentationNodeMerger : DocumentationNodeMerger {
+    override fun invoke(modules: Collection<Module>, context: DokkaContext): Module =
+        Module(
+            merge(
+                modules.flatMap { it.packages },
+                Package::mergeWith
+            )
+        )
+}
+
+private fun <T: DocumentationNode> merge(elements: List<T>, reducer: (T, T) -> T): List<T> =
+    elements.groupingBy { it.dri }
+        .reduce { _, left, right -> reducer(left, right)}
+        .values.toList()
+
+fun PlatformInfo.mergeWith(other: PlatformInfo?) = BasePlatformInfo(
+    docTag,
+    links,
+    (platformData + (other?.platformData ?: emptyList())).distinct()
+)
+
+fun ClassPlatformInfo.mergeWith(other: ClassPlatformInfo?) = ClassPlatformInfo(
+    info.mergeWith(other?.info),
+    (inherited + (other?.inherited ?: emptyList())).distinct()
+)
+
+fun List<ClassPlatformInfo>.mergeClassPlatformInfo() : List<ClassPlatformInfo> =
+    groupingBy { it.docTag.toString() + it.links + it.inherited}.reduce {
+            _, left, right -> left.mergeWith(right)
+    }.values.toList()
+
+fun List<PlatformInfo>.merge() : List<PlatformInfo> =
+    groupingBy { it.docTag.toString() + it.links }.reduce {
+        _, left, right -> left.mergeWith(right)
+    }.values.toList()
+
+fun Function.mergeWith(other: Function) = Function(
+    dri,
+    name,
+    returnType,
+    isConstructor,
+    if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
+    merge(parameters + other.parameters, Parameter::mergeWith),
+    expected?.mergeWith(other.expected),
+    (actual + other.actual).merge()
+)
+
+fun Property.mergeWith(other: Property) = Property(
+    dri,
+    name,
+    if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
+    expected?.mergeWith(other.expected),
+    (actual + other.actual).merge()
+)
+
+fun Class.mergeWith(other: Class) = Class(
+    dri,
+    name,
+    kind,
+    merge(constructors + other.constructors, Function::mergeWith),
+    merge(functions + other.functions, Function::mergeWith),
+    merge(properties + other.properties, Property::mergeWith),
+    merge(classes + other.classes, Class::mergeWith),
+    expected?.mergeWith(other.expected),
+    (actual + other.actual).mergeClassPlatformInfo()
+)
+
+fun Parameter.mergeWith(other: Parameter) = Parameter(
+    dri,
+    name,
+    type,
+    (actual + other.actual).merge()
+)
+
+fun Package.mergeWith(other: Package) = Package(
+    dri,
+    merge(functions + other.functions, Function::mergeWith),
+    merge(properties + other.properties, Property::mergeWith),
+    merge(classes + other.classes, Class::mergeWith)
+)
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/documentation/DefaultDocumentationToPageTranslator.kt b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationToPageTranslator.kt
new file mode 100644
index 00000000..236d0864
--- /dev/null
+++ b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationToPageTranslator.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka.transformers.documentation
+
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.pages.DefaultPageBuilder
+import org.jetbrains.dokka.pages.DefaultPageContentBuilder
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.single
+
+
+object DefaultDocumentationToPageTranslator : DocumentationToPageTranslator {
+    override fun invoke(module: Module, context: DokkaContext): ModulePageNode =
+        DefaultPageBuilder { node, kind, operation ->
+            DefaultPageContentBuilder.group(
+                node.dri,
+                node.platformData,
+                kind,
+                context.single(CoreExtensions.markdownToContentConverterFactory).invoke(context),
+                context.logger,
+                operation
+            )
+        }.pageForModule(module)
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/documentation/DocumentationNodeMerger.kt b/core/src/main/kotlin/transformers/documentation/DocumentationNodeMerger.kt
new file mode 100644
index 00000000..0423f47c
--- /dev/null
+++ b/core/src/main/kotlin/transformers/documentation/DocumentationNodeMerger.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka.transformers.documentation
+
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.plugability.DokkaContext
+
+interface DocumentationNodeMerger {
+    operator fun invoke(modules: Collection<Module>, context: DokkaContext): Module
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/documentation/DocumentationNodeTransformer.kt b/core/src/main/kotlin/transformers/documentation/DocumentationNodeTransformer.kt
new file mode 100644
index 00000000..8ac4a7c2
--- /dev/null
+++ b/core/src/main/kotlin/transformers/documentation/DocumentationNodeTransformer.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka.transformers.documentation
+
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.plugability.DokkaContext
+
+interface DocumentationNodeTransformer {
+    operator fun invoke(original: Module, context: DokkaContext): Module
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/documentation/DocumentationToPageTranslator.kt b/core/src/main/kotlin/transformers/documentation/DocumentationToPageTranslator.kt
new file mode 100644
index 00000000..ffe34226
--- /dev/null
+++ b/core/src/main/kotlin/transformers/documentation/DocumentationToPageTranslator.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.transformers.documentation
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.Model.DocumentationNode
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+
+interface DocumentationToPageTranslator {
+    operator fun invoke(module: Module, context: DokkaContext): ModulePageNode
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt b/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt
new file mode 100644
index 00000000..286835f9
--- /dev/null
+++ b/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka.transformers.pages
+
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+
+interface PageNodeTransformer {
+    operator fun invoke(input: ModulePageNode, dokkaContext: DokkaContext): ModulePageNode
+}
\ No newline at end of file
-- 
cgit