aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/build.gradle103
-rwxr-xr-xcore/gradlew164
-rw-r--r--core/gradlew.bat90
-rw-r--r--core/settings.gradle2
-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
-rw-r--r--core/src/test/kotlin/TestAPI.kt214
-rw-r--r--core/src/test/kotlin/format/HtmlFormatTest.kt157
-rw-r--r--core/src/test/kotlin/format/MarkdownFormatTest.kt218
-rw-r--r--core/src/test/kotlin/format/PackageDocsTest.kt18
-rw-r--r--core/src/test/kotlin/javadoc/JavadocTest.kt44
-rw-r--r--core/src/test/kotlin/markdown/ParserTest.kt142
-rw-r--r--core/src/test/kotlin/model/ClassTest.kt275
-rw-r--r--core/src/test/kotlin/model/CommentTest.kt153
-rw-r--r--core/src/test/kotlin/model/FunctionTest.kt227
-rw-r--r--core/src/test/kotlin/model/JavaTest.kt197
-rw-r--r--core/src/test/kotlin/model/KotlinAsJavaTest.kt40
-rw-r--r--core/src/test/kotlin/model/LinkTest.kt48
-rw-r--r--core/src/test/kotlin/model/PackageTest.kt86
-rw-r--r--core/src/test/kotlin/model/PropertyTest.kt103
-rw-r--r--core/testdata/classes/annotatedClass.kt1
-rw-r--r--core/testdata/classes/annotatedClassWithAnnotationParameters.kt1
-rw-r--r--core/testdata/classes/classWithCompanionObject.kt7
-rw-r--r--core/testdata/classes/classWithConstructor.kt1
-rw-r--r--core/testdata/classes/classWithFunction.kt4
-rw-r--r--core/testdata/classes/classWithProperty.kt3
-rw-r--r--core/testdata/classes/companionObjectExtension.kt10
-rw-r--r--core/testdata/classes/dataClass.kt1
-rw-r--r--core/testdata/classes/emptyClass.kt3
-rw-r--r--core/testdata/classes/emptyObject.kt3
-rw-r--r--core/testdata/classes/genericClass.kt3
-rw-r--r--core/testdata/classes/indirectOverride.kt9
-rw-r--r--core/testdata/classes/innerClass.kt5
-rw-r--r--core/testdata/classes/javaAnnotationClass.kt5
-rw-r--r--core/testdata/classes/notOpenClass.kt7
-rw-r--r--core/testdata/classes/sealedClass.kt1
-rw-r--r--core/testdata/classes/secondaryConstructor.kt5
-rw-r--r--core/testdata/comments/directive.kt35
-rw-r--r--core/testdata/comments/emptyDoc.kt1
-rw-r--r--core/testdata/comments/emptyDocButComment.kt2
-rw-r--r--core/testdata/comments/emptySection.kt6
-rw-r--r--core/testdata/comments/multilineDoc.kt7
-rw-r--r--core/testdata/comments/multilineDocWithComment.kt8
-rw-r--r--core/testdata/comments/multilineSection.kt7
-rw-r--r--core/testdata/comments/oneLineDoc.kt2
-rw-r--r--core/testdata/comments/oneLineDocWithComment.kt3
-rw-r--r--core/testdata/comments/oneLineDocWithEmptyLine.kt3
-rw-r--r--core/testdata/comments/section1.kt5
-rw-r--r--core/testdata/comments/section2.kt6
-rw-r--r--core/testdata/format/accessor.kt5
-rw-r--r--core/testdata/format/accessor.md18
-rw-r--r--core/testdata/format/annotatedTypeParameter.kt2
-rw-r--r--core/testdata/format/annotatedTypeParameter.md8
-rw-r--r--core/testdata/format/annotationClass.kt1
-rw-r--r--core/testdata/format/annotationClass.md14
-rw-r--r--core/testdata/format/annotationParams.kt1
-rw-r--r--core/testdata/format/annotationParams.md8
-rw-r--r--core/testdata/format/annotations.kt6
-rw-r--r--core/testdata/format/annotations.md26
-rw-r--r--core/testdata/format/bracket.html14
-rw-r--r--core/testdata/format/bracket.kt4
-rw-r--r--core/testdata/format/brokenLink.html14
-rw-r--r--core/testdata/format/brokenLink.kt4
-rw-r--r--core/testdata/format/classWithCompanionObject.html46
-rw-r--r--core/testdata/format/classWithCompanionObject.kt7
-rw-r--r--core/testdata/format/classWithCompanionObject.md26
-rw-r--r--core/testdata/format/codeSpan.html14
-rw-r--r--core/testdata/format/codeSpan.kt4
-rw-r--r--core/testdata/format/companionObjectExtension.kt10
-rw-r--r--core/testdata/format/companionObjectExtension.md23
-rw-r--r--core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.html37
-rw-r--r--core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt6
-rw-r--r--core/testdata/format/crossLanguage/kotlinExtendsJava/test/Foo.java6
-rw-r--r--core/testdata/format/deprecated.class.html41
-rw-r--r--core/testdata/format/deprecated.kt5
-rw-r--r--core/testdata/format/deprecated.package.html43
-rw-r--r--core/testdata/format/emptyDescription.kt5
-rw-r--r--core/testdata/format/emptyDescription.md11
-rw-r--r--core/testdata/format/entity.html26
-rw-r--r--core/testdata/format/entity.kt4
-rw-r--r--core/testdata/format/enumClass.kt4
-rw-r--r--core/testdata/format/enumClass.md15
-rw-r--r--core/testdata/format/enumClass.value.md8
-rw-r--r--core/testdata/format/extensionFunctionParameter.kt1
-rw-r--r--core/testdata/format/extensionFunctionParameter.md8
-rw-r--r--core/testdata/format/extensions.class.md16
-rw-r--r--core/testdata/format/extensions.kt19
-rw-r--r--core/testdata/format/extensions.package.md11
-rw-r--r--core/testdata/format/functionWithDefaultParameter.kt1
-rw-r--r--core/testdata/format/functionWithDefaultParameter.md8
-rw-r--r--core/testdata/format/htmlEscaping.html14
-rw-r--r--core/testdata/format/htmlEscaping.kt4
-rw-r--r--core/testdata/format/inheritedExtensions.kt11
-rw-r--r--core/testdata/format/inheritedExtensions.md21
-rw-r--r--core/testdata/format/inheritedMembers.kt12
-rw-r--r--core/testdata/format/inheritedMembers.md38
-rw-r--r--core/testdata/format/javaCodeInParam.java5
-rw-r--r--core/testdata/format/javaCodeInParam.md21
-rw-r--r--core/testdata/format/javaCodeLiteralTags.java6
-rw-r--r--core/testdata/format/javaCodeLiteralTags.md23
-rw-r--r--core/testdata/format/javaDeprecated.html14
-rw-r--r--core/testdata/format/javaDeprecated.java5
-rw-r--r--core/testdata/format/javaLinkTag.html36
-rw-r--r--core/testdata/format/javaLinkTag.java6
-rw-r--r--core/testdata/format/javaLinkTagWithLabel.html36
-rw-r--r--core/testdata/format/javaLinkTagWithLabel.java6
-rw-r--r--core/testdata/format/javaSeeTag.html38
-rw-r--r--core/testdata/format/javaSeeTag.java6
-rw-r--r--core/testdata/format/javaSpaceInAuthor.java5
-rw-r--r--core/testdata/format/javaSpaceInAuthor.md19
-rw-r--r--core/testdata/format/javaSupertype.html35
-rw-r--r--core/testdata/format/javaSupertype.java8
-rw-r--r--core/testdata/format/javadocHtml.java14
-rw-r--r--core/testdata/format/javadocHtml.md27
-rw-r--r--core/testdata/format/javadocOrderedList.java8
-rw-r--r--core/testdata/format/javadocOrderedList.md20
-rw-r--r--core/testdata/format/linkWithLabel.html37
-rw-r--r--core/testdata/format/linkWithLabel.kt6
-rw-r--r--core/testdata/format/nullability.kt5
-rw-r--r--core/testdata/format/nullability.md20
-rw-r--r--core/testdata/format/operatorOverloading.kt3
-rw-r--r--core/testdata/format/operatorOverloading.md8
-rw-r--r--core/testdata/format/orderedList.html30
-rw-r--r--core/testdata/format/orderedList.kt8
-rw-r--r--core/testdata/format/overloads.html23
-rw-r--r--core/testdata/format/overloads.kt5
-rw-r--r--core/testdata/format/overloadsWithDescription.html21
-rw-r--r--core/testdata/format/overloadsWithDescription.kt15
-rw-r--r--core/testdata/format/overloadsWithDifferentDescriptions.html30
-rw-r--r--core/testdata/format/overloadsWithDifferentDescriptions.kt15
-rw-r--r--core/testdata/format/overridingFunction.kt7
-rw-r--r--core/testdata/format/overridingFunction.md9
-rw-r--r--core/testdata/format/paramTag.kt6
-rw-r--r--core/testdata/format/paramTag.md14
-rw-r--r--core/testdata/format/parameterAnchor.html17
-rw-r--r--core/testdata/format/parameterAnchor.kt6
-rw-r--r--core/testdata/format/parenthesis.html14
-rw-r--r--core/testdata/format/parenthesis.kt4
-rw-r--r--core/testdata/format/propertyVar.kt1
-rw-r--r--core/testdata/format/propertyVar.md8
-rw-r--r--core/testdata/format/reifiedTypeParameter.kt3
-rw-r--r--core/testdata/format/reifiedTypeParameter.md8
-rw-r--r--core/testdata/format/see.html28
-rw-r--r--core/testdata/format/see.kt12
-rw-r--r--core/testdata/format/starProjection.kt3
-rw-r--r--core/testdata/format/starProjection.md8
-rw-r--r--core/testdata/format/summarizeSignatures.kt20
-rw-r--r--core/testdata/format/summarizeSignatures.md23
-rw-r--r--core/testdata/format/summarizeSignaturesProperty.kt20
-rw-r--r--core/testdata/format/summarizeSignaturesProperty.md23
-rw-r--r--core/testdata/format/throwsTag.kt5
-rw-r--r--core/testdata/format/throwsTag.md13
-rw-r--r--core/testdata/format/tripleBackticks.html16
-rw-r--r--core/testdata/format/tripleBackticks.kt7
-rw-r--r--core/testdata/format/typeLink.html24
-rw-r--r--core/testdata/format/typeLink.kt5
-rw-r--r--core/testdata/format/typeParameterBounds.kt6
-rw-r--r--core/testdata/format/typeParameterBounds.md11
-rw-r--r--core/testdata/format/typeParameterVariance.kt2
-rw-r--r--core/testdata/format/typeParameterVariance.md14
-rw-r--r--core/testdata/format/typeProjectionVariance.kt1
-rw-r--r--core/testdata/format/typeProjectionVariance.md8
-rw-r--r--core/testdata/format/varargsFunction.kt1
-rw-r--r--core/testdata/format/varargsFunction.md8
-rw-r--r--core/testdata/functions/annotatedFunction.kt2
-rw-r--r--core/testdata/functions/annotatedFunctionWithAnnotationParameters.kt7
-rw-r--r--core/testdata/functions/function.kt5
-rw-r--r--core/testdata/functions/functionWithAnnotatedParam.kt7
-rw-r--r--core/testdata/functions/functionWithDefaultParameter.kt1
-rw-r--r--core/testdata/functions/functionWithNoinlineParam.kt2
-rw-r--r--core/testdata/functions/functionWithNotDocumentedAnnotation.kt2
-rw-r--r--core/testdata/functions/functionWithParams.kt8
-rw-r--r--core/testdata/functions/functionWithReceiver.kt11
-rw-r--r--core/testdata/functions/genericFunction.kt5
-rw-r--r--core/testdata/functions/genericFunctionWithConstraints.kt6
-rw-r--r--core/testdata/functions/inlineFunction.kt2
-rw-r--r--core/testdata/java/annotatedAnnotation.java6
-rw-r--r--core/testdata/java/arrayType.java5
-rw-r--r--core/testdata/java/constructors.java5
-rw-r--r--core/testdata/java/deprecation.java6
-rw-r--r--core/testdata/java/enumValues.java3
-rw-r--r--core/testdata/java/field.java4
-rw-r--r--core/testdata/java/inheritorLinks.java7
-rw-r--r--core/testdata/java/innerClass.java4
-rw-r--r--core/testdata/java/javaLangObject.java3
-rw-r--r--core/testdata/java/member.java11
-rw-r--r--core/testdata/java/memberWithModifiers.java12
-rw-r--r--core/testdata/java/staticMethod.java4
-rw-r--r--core/testdata/java/superClass.java2
-rw-r--r--core/testdata/java/typeParameter.java3
-rw-r--r--core/testdata/java/varargs.java3
-rw-r--r--core/testdata/javadoc/obj.kt7
-rw-r--r--core/testdata/javadoc/types.kt4
-rw-r--r--core/testdata/links/linkToJDK.kt0
-rw-r--r--core/testdata/links/linkToMember.kt6
-rw-r--r--core/testdata/links/linkToParam.kt5
-rw-r--r--core/testdata/links/linkToQualifiedMember.kt6
-rw-r--r--core/testdata/links/linkToSelf.kt6
-rw-r--r--core/testdata/markdown/spec.txt6150
-rw-r--r--core/testdata/packagedocs/stdlib.md11
-rw-r--r--core/testdata/packages/dottedNamePackage.kt1
-rw-r--r--core/testdata/packages/rootPackage.kt0
-rw-r--r--core/testdata/packages/simpleNamePackage.kt1
-rw-r--r--core/testdata/packages/simpleNamePackage2.kt1
-rw-r--r--core/testdata/properties/annotatedProperty.kt1
-rw-r--r--core/testdata/properties/propertyOverride.kt7
-rw-r--r--core/testdata/properties/propertyWithReceiver.kt2
-rw-r--r--core/testdata/properties/valueProperty.kt1
-rw-r--r--core/testdata/properties/valuePropertyWithGetter.kt2
-rw-r--r--core/testdata/properties/variableProperty.kt1
-rw-r--r--core/testdata/properties/variablePropertyWithAccessors.kt4
270 files changed, 16689 insertions, 0 deletions
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 00000000..49d6d4d6
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,103 @@
+import javax.tools.ToolProvider
+
+group 'org.jetbrains.dokka'
+version dokka_version
+
+buildscript {
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.2'
+ }
+ repositories {
+ jcenter()
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'kotlin'
+apply plugin: 'com.github.johnrengelman.shadow'
+apply plugin: 'maven-publish'
+apply plugin: 'com.jfrog.bintray'
+
+sourceCompatibility = 1.5
+
+repositories {
+ mavenCentral()
+}
+
+configurations {
+ provided
+}
+
+dependencies {
+ compile "com.google.inject:guice:4.0"
+ compile "com.github.spullara.cli-parser:cli-parser:1.1.1"
+ compile "org.jsoup:jsoup:1.8.3"
+
+ compile files("../lib/intellij-core-analysis.jar")
+ compile files("../lib/kotlin-for-upsource.jar")
+ compile files("../lib/markdown.jar")
+ compile files("../lib/picocontainer.jar")
+
+ provided files(((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs().findAll { it.path.endsWith("jar") })
+ provided "org.apache.ant:ant:1.9.6"
+
+ runtime "org.fusesource.jansi:jansi:1.11"
+
+ runtime files("../lib/trove4j.jar")
+ runtime files("../lib/jdom.jar")
+ runtime files("../lib/protobuf-2.5.0-lite.jar")
+ runtime files("../lib/asm-all.jar")
+ runtime files("../lib/jps-model.jar")
+
+ testCompile group: 'junit', name: 'junit', version: '4.11'
+}
+
+tasks.withType(AbstractCompile) {
+ classpath += configurations.provided
+}
+
+tasks.withType(Test) {
+ classpath += configurations.provided
+}
+
+jar {
+ manifest {
+ attributes "Implementation-Title": "Dokka Kotlin Documentation tool"
+ attributes "Implementation-Version": version
+ attributes "Main-Class": "org.jetbrains.dokka.MainKt"
+ }
+}
+
+shadowJar {
+ baseName = 'dokka-fatjar'
+ classifier = ''
+}
+
+publishing {
+ publications {
+ shadow(MavenPublication) {
+ from components.shadow
+ artifactId = 'dokka-fatjar'
+ }
+ }
+}
+
+bintray {
+ user = System.getenv('BINTRAY_USER')
+ key = System.getenv('BINTRAY_KEY')
+
+ pkg {
+ repo = 'dokka'
+ name = 'dokka'
+ userOrg = 'kotlin'
+ desc = 'Dokka, the Kotlin documentation tool'
+ vcsUrl = 'https://github.com/kotlin/dokka.git'
+ licenses = ['Apache-2.0']
+ version {
+ name = dokka_version
+ }
+ }
+
+ publications = ['shadow']
+}
diff --git a/core/gradlew b/core/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/core/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/core/gradlew.bat b/core/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/core/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/core/settings.gradle b/core/settings.gradle
new file mode 100644
index 00000000..17bdad4c
--- /dev/null
+++ b/core/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'core'
+
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
diff --git a/core/src/test/kotlin/TestAPI.kt b/core/src/test/kotlin/TestAPI.kt
new file mode 100644
index 00000000..d7833e36
--- /dev/null
+++ b/core/src/test/kotlin/TestAPI.kt
@@ -0,0 +1,214 @@
+package org.jetbrains.dokka.tests
+
+import com.google.inject.Guice
+import com.intellij.openapi.application.PathManager
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.io.FileUtil
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Utilities.DokkaModule
+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.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.config.ContentRoot
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.junit.Assert
+import java.io.File
+import kotlin.test.fail
+
+public fun verifyModel(vararg roots: ContentRoot,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ format: String = "html",
+ verifier: (DocumentationModule) -> Unit) {
+ val messageCollector = object : MessageCollector {
+ override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
+ when (severity) {
+ CompilerMessageSeverity.WARNING,
+ CompilerMessageSeverity.LOGGING,
+ CompilerMessageSeverity.OUTPUT,
+ CompilerMessageSeverity.INFO,
+ CompilerMessageSeverity.ERROR -> {
+ println("$severity: $message at $location")
+ }
+ CompilerMessageSeverity.EXCEPTION -> {
+ fail("$severity: $message at $location")
+ }
+ }
+ }
+ }
+
+ val environment = AnalysisEnvironment(messageCollector)
+ environment.apply {
+ if (withJdk || withKotlinRuntime) {
+ val stringRoot = PathManager.getResourceRoot(String::class.java, "/java/lang/String.class")
+ addClasspath(File(stringRoot))
+ }
+ if (withKotlinRuntime) {
+ val kotlinPairRoot = PathManager.getResourceRoot(Pair::class.java, "/kotlin/Pair.class")
+ addClasspath(File(kotlinPairRoot))
+ }
+ addRoots(roots.toList())
+ }
+ val options = DocumentationOptions("", format, includeNonPublic = true, skipEmptyPackages = false, sourceLinks = listOf<SourceLinkDefinition>())
+ val injector = Guice.createInjector(DokkaModule(environment, options, DokkaConsoleLogger))
+ val documentation = buildDocumentationModule(injector, "test")
+ verifier(documentation)
+ Disposer.dispose(environment)
+}
+
+public fun verifyModel(source: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ format: String = "html",
+ verifier: (DocumentationModule) -> Unit) {
+ if (!File(source).exists()) {
+ throw IllegalArgumentException("Can't find test data file $source")
+ }
+ verifyModel(contentRootFromPath(source),
+ withJdk = withJdk,
+ withKotlinRuntime = withKotlinRuntime,
+ format = format,
+ verifier = verifier)
+}
+
+public fun verifyPackageMember(source: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ verifier: (DocumentationNode) -> Unit) {
+ verifyModel(source, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) { model ->
+ val pkg = model.members.single()
+ verifier(pkg.members.single())
+ }
+}
+
+public fun verifyJavaModel(source: String,
+ withKotlinRuntime: Boolean = false,
+ verifier: (DocumentationModule) -> Unit) {
+ val tempDir = FileUtil.createTempDirectory("dokka", "")
+ try {
+ val sourceFile = File(source)
+ FileUtil.copy(sourceFile, File(tempDir, sourceFile.name))
+ verifyModel(JavaSourceRoot(tempDir, null), withJdk = true, withKotlinRuntime = withKotlinRuntime, verifier = verifier)
+ }
+ finally {
+ FileUtil.delete(tempDir)
+ }
+}
+
+public fun verifyJavaPackageMember(source: String,
+ withKotlinRuntime: Boolean = false,
+ verifier: (DocumentationNode) -> Unit) {
+ verifyJavaModel(source, withKotlinRuntime) { model ->
+ val pkg = model.members.single()
+ verifier(pkg.members.single())
+ }
+}
+
+public fun verifyOutput(roots: Array<ContentRoot>,
+ outputExtension: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyModel(*roots, withJdk = withJdk, withKotlinRuntime = withKotlinRuntime) {
+ verifyModelOutput(it, outputExtension, outputGenerator, roots.first().path)
+ }
+}
+
+private fun verifyModelOutput(it: DocumentationModule,
+ outputExtension: String,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit,
+ sourcePath: String) {
+ val output = StringBuilder()
+ outputGenerator(it, output)
+ val ext = outputExtension.removePrefix(".")
+ val path = sourcePath
+ val expectedOutput = File(path.replaceAfterLast(".", ext, path + "." + ext)).readText()
+ assertEqualsIgnoringSeparators(expectedOutput, output.toString())
+}
+
+public fun verifyOutput(path: String,
+ outputExtension: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyOutput(arrayOf(contentRootFromPath(path)), outputExtension, withJdk, withKotlinRuntime, outputGenerator)
+}
+
+public fun verifyJavaOutput(path: String,
+ outputExtension: String,
+ withKotlinRuntime: Boolean = false,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyJavaModel(path, withKotlinRuntime) { model ->
+ verifyModelOutput(model, outputExtension, outputGenerator, path)
+ }
+}
+
+public fun assertEqualsIgnoringSeparators(expectedOutput: String, output: String) {
+ Assert.assertEquals(expectedOutput.replace("\r\n", "\n"), output.replace("\r\n", "\n"))
+}
+
+fun StringBuilder.appendChildren(node: ContentBlock): StringBuilder {
+ for (child in node.children) {
+ val childText = child.toTestString()
+ append(childText)
+ }
+ return this
+}
+
+fun StringBuilder.appendNode(node: ContentNode): StringBuilder {
+ when (node) {
+ is ContentText -> {
+ append(node.text)
+ }
+ is ContentEmphasis -> append("*").appendChildren(node).append("*")
+ is ContentBlockCode -> {
+ appendln("[code]")
+ appendChildren(node)
+ appendln()
+ appendln("[/code]")
+ }
+ is ContentNodeLink -> {
+ append("[")
+ appendChildren(node)
+ append(" -> ")
+ append(node.node.toString())
+ append("]")
+ }
+ is ContentBlock -> {
+ appendChildren(node)
+ }
+ is ContentEmpty -> { /* nothing */ }
+ else -> throw IllegalStateException("Don't know how to format node $node")
+ }
+ return this
+}
+
+fun ContentNode.toTestString(): String {
+ val node = this
+ return StringBuilder().apply {
+ appendNode(node)
+ }.toString()
+}
+
+class InMemoryLocation(override val path: String): Location {
+ override fun relativePathTo(other: Location, anchor: String?): String =
+ if (anchor != null) other.path + "#" + anchor else other.path
+}
+
+object InMemoryLocationService: LocationService {
+ override fun location(qualifiedName: List<String>, hasMembers: Boolean) =
+ InMemoryLocation(relativePathToNode(qualifiedName, hasMembers))
+
+ override val root: Location
+ get() = InMemoryLocation("")
+}
+
+val tempLocation = InMemoryLocation("")
+
+val ContentRoot.path: String
+ get() = when(this) {
+ is KotlinSourceRoot -> path
+ is JavaSourceRoot -> file.path
+ else -> throw UnsupportedOperationException()
+ }
diff --git a/core/src/test/kotlin/format/HtmlFormatTest.kt b/core/src/test/kotlin/format/HtmlFormatTest.kt
new file mode 100644
index 00000000..593dbfe6
--- /dev/null
+++ b/core/src/test/kotlin/format/HtmlFormatTest.kt
@@ -0,0 +1,157 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.HtmlFormatService
+import org.jetbrains.dokka.HtmlTemplateService
+import org.jetbrains.dokka.KotlinLanguageService
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.junit.Test
+import java.io.File
+
+public class HtmlFormatTest {
+ private val htmlService = HtmlFormatService(InMemoryLocationService, KotlinLanguageService(), HtmlTemplateService.default())
+
+ @Test fun classWithCompanionObject() {
+ verifyOutput("testdata/format/classWithCompanionObject.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun htmlEscaping() {
+ verifyOutput("testdata/format/htmlEscaping.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun overloads() {
+ verifyOutput("testdata/format/overloads.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members)
+ }
+ }
+
+ @Test fun overloadsWithDescription() {
+ verifyOutput("testdata/format/overloadsWithDescription.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun overloadsWithDifferentDescriptions() {
+ verifyOutput("testdata/format/overloadsWithDifferentDescriptions.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun deprecated() {
+ verifyOutput("testdata/format/deprecated.kt", ".package.html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members)
+ }
+ verifyOutput("testdata/format/deprecated.kt", ".class.html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun brokenLink() {
+ verifyOutput("testdata/format/brokenLink.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun codeSpan() {
+ verifyOutput("testdata/format/codeSpan.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun parenthesis() {
+ verifyOutput("testdata/format/parenthesis.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun bracket() {
+ verifyOutput("testdata/format/bracket.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun see() {
+ verifyOutput("testdata/format/see.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun tripleBackticks() {
+ verifyOutput("testdata/format/tripleBackticks.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun typeLink() {
+ verifyOutput("testdata/format/typeLink.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar"} )
+ }
+ }
+
+ @Test fun parameterAnchor() {
+ verifyOutput("testdata/format/parameterAnchor.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaSupertypeLink() {
+ verifyJavaOutput("testdata/format/javaSupertype.java", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.single { it.name == "C"}.members.filter { it.name == "Bar"} )
+ }
+ }
+
+ @Test fun javaLinkTag() {
+ verifyJavaOutput("testdata/format/javaLinkTag.java", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaLinkTagWithLabel() {
+ verifyJavaOutput("testdata/format/javaLinkTagWithLabel.java", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaSeeTag() {
+ verifyJavaOutput("testdata/format/javaSeeTag.java", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaDeprecated() {
+ verifyJavaOutput("testdata/format/javaDeprecated.java", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.single { it.name == "Foo" }.members.filter { it.name == "foo" })
+ }
+ }
+
+ @Test fun crossLanguageKotlinExtendsJava() {
+ verifyOutput(arrayOf(KotlinSourceRoot("testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt"),
+ JavaSourceRoot(File("testdata/format/crossLanguage/kotlinExtendsJava"), null)),
+ ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+
+ @Test fun orderedList() {
+ verifyOutput("testdata/format/orderedList.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+
+ @Test fun linkWithLabel() {
+ verifyOutput("testdata/format/linkWithLabel.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+
+ @Test fun entity() {
+ verifyOutput("testdata/format/entity.kt", ".html") { model, output ->
+ htmlService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+}
+
diff --git a/core/src/test/kotlin/format/MarkdownFormatTest.kt b/core/src/test/kotlin/format/MarkdownFormatTest.kt
new file mode 100644
index 00000000..e2339707
--- /dev/null
+++ b/core/src/test/kotlin/format/MarkdownFormatTest.kt
@@ -0,0 +1,218 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.KotlinLanguageService
+import org.jetbrains.dokka.MarkdownFormatService
+import org.junit.Test
+
+public class MarkdownFormatTest {
+ private val markdownService = MarkdownFormatService(InMemoryLocationService, KotlinLanguageService())
+
+ @Test fun emptyDescription() {
+ verifyOutput("testdata/format/emptyDescription.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun classWithCompanionObject() {
+ verifyOutput("testdata/format/classWithCompanionObject.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun annotations() {
+ verifyOutput("testdata/format/annotations.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun annotationClass() {
+ verifyOutput("testdata/format/annotationClass.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun annotationParams() {
+ verifyOutput("testdata/format/annotationParams.kt", ".md", withKotlinRuntime = true) { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun extensions() {
+ verifyOutput("testdata/format/extensions.kt", ".package.md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members)
+ }
+ verifyOutput("testdata/format/extensions.kt", ".class.md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun enumClass() {
+ verifyOutput("testdata/format/enumClass.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ verifyOutput("testdata/format/enumClass.kt", ".value.md") { model, output ->
+ val enumClassNode = model.members.single().members[0]
+ markdownService.appendNodes(tempLocation, output,
+ enumClassNode.members.filter { it.name == "LOCAL_CONTINUE_AND_BREAK" })
+ }
+ }
+
+ @Test fun varargsFunction() {
+ verifyOutput("testdata/format/varargsFunction.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun overridingFunction() {
+ verifyOutput("testdata/format/overridingFunction.kt", ".md") { model, output ->
+ val classMembers = model.members.single().members.first { it.name == "D" }.members
+ markdownService.appendNodes(tempLocation, output, classMembers.filter { it.name == "f" })
+ }
+
+ }
+
+ @Test fun propertyVar() {
+ verifyOutput("testdata/format/propertyVar.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun functionWithDefaultParameter() {
+ verifyOutput("testdata/format/functionWithDefaultParameter.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun accessor() {
+ verifyOutput("testdata/format/accessor.kt", ".md") { model, output ->
+ val propertyNode = model.members.single().members.first { it.name == "C" }.members.filter { it.name == "x" }
+ markdownService.appendNodes(tempLocation, output, propertyNode)
+ }
+ }
+
+ @Test fun paramTag() {
+ verifyOutput("testdata/format/paramTag.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun throwsTag() {
+ verifyOutput("testdata/format/throwsTag.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun typeParameterBounds() {
+ verifyOutput("testdata/format/typeParameterBounds.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun typeParameterVariance() {
+ verifyOutput("testdata/format/typeParameterVariance.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun typeProjectionVariance() {
+ verifyOutput("testdata/format/typeProjectionVariance.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javadocHtml() {
+ verifyJavaOutput("testdata/format/javadocHtml.java", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaCodeLiteralTags() {
+ verifyJavaOutput("testdata/format/javaCodeLiteralTags.java", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaCodeInParam() {
+ verifyJavaOutput("testdata/format/javaCodeInParam.java", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun javaSpaceInAuthor() {
+ verifyJavaOutput("testdata/format/javaSpaceInAuthor.java", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun nullability() {
+ verifyOutput("testdata/format/nullability.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun operatorOverloading() {
+ verifyOutput("testdata/format/operatorOverloading.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members.single { it.name == "C" }.members.filter { it.name == "plus" })
+ }
+ }
+
+ @Test fun javadocOrderedList() {
+ verifyJavaOutput("testdata/format/javadocOrderedList.java", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+
+ @Test fun companionObjectExtension() {
+ verifyOutput("testdata/format/companionObjectExtension.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Foo" })
+ }
+ }
+
+ @Test fun starProjection() {
+ verifyOutput("testdata/format/starProjection.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun extensionFunctionParameter() {
+ verifyOutput("testdata/format/extensionFunctionParameter.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun summarizeSignatures() {
+ verifyOutput("testdata/format/summarizeSignatures.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members)
+ }
+ }
+
+ @Test fun summarizeSignaturesProperty() {
+ verifyOutput("testdata/format/summarizeSignaturesProperty.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members)
+ }
+ }
+
+ @Test fun reifiedTypeParameter() {
+ verifyOutput("testdata/format/reifiedTypeParameter.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun annotatedTypeParameter() {
+ verifyOutput("testdata/format/annotatedTypeParameter.kt", ".md", withKotlinRuntime = true) { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members)
+ }
+ }
+
+ @Test fun inheritedMembers() {
+ verifyOutput("testdata/format/inheritedMembers.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+
+ @Test fun inheritedExtensions() {
+ verifyOutput("testdata/format/inheritedExtensions.kt", ".md") { model, output ->
+ markdownService.appendNodes(tempLocation, output, model.members.single().members.filter { it.name == "Bar" })
+ }
+ }
+}
diff --git a/core/src/test/kotlin/format/PackageDocsTest.kt b/core/src/test/kotlin/format/PackageDocsTest.kt
new file mode 100644
index 00000000..4d7852da
--- /dev/null
+++ b/core/src/test/kotlin/format/PackageDocsTest.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.dokka.tests.format
+
+import org.jetbrains.dokka.ContentBlock
+import org.jetbrains.dokka.ContentText
+import org.jetbrains.dokka.DokkaConsoleLogger
+import org.jetbrains.dokka.PackageDocs
+import org.junit.Test
+import kotlin.test.assertEquals
+
+public class PackageDocsTest {
+ @Test fun verifyParse() {
+ val docs = PackageDocs(null, DokkaConsoleLogger)
+ docs.parse("testdata/packagedocs/stdlib.md", null)
+ val packageContent = docs.packageContent["kotlin"]!!
+ val block = (packageContent.children.single() as ContentBlock).children.first() as ContentText
+ assertEquals("Core functions and types", block.text)
+ }
+}
diff --git a/core/src/test/kotlin/javadoc/JavadocTest.kt b/core/src/test/kotlin/javadoc/JavadocTest.kt
new file mode 100644
index 00000000..4f0049ac
--- /dev/null
+++ b/core/src/test/kotlin/javadoc/JavadocTest.kt
@@ -0,0 +1,44 @@
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.DokkaConsoleLogger
+import org.jetbrains.dokka.tests.verifyModel
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class JavadocTest {
+ @Test fun testTypes() {
+ verifyModel("testdata/javadoc/types.kt", format = "javadoc", withJdk = true) { model ->
+ val doc = ModuleNodeAdapter(model, StandardReporter(DokkaConsoleLogger), "")
+ val classDoc = doc.classNamed("foo.TypesKt")!!
+ val method = classDoc.methods().find { it.name() == "foo" }!!
+
+ val type = method.returnType()
+ assertFalse(type.asClassDoc().isIncluded)
+ assertEquals("java.lang.String", type.qualifiedTypeName())
+ assertEquals("java.lang.String", type.asClassDoc().qualifiedName())
+
+ val params = method.parameters()
+ assertTrue(params[0].type().isPrimitive)
+ assertFalse(params[1].type().asClassDoc().isIncluded)
+ }
+ }
+
+ @Test fun testObject() {
+ verifyModel("testdata/javadoc/obj.kt", format = "javadoc") { model ->
+ val doc = ModuleNodeAdapter(model, StandardReporter(DokkaConsoleLogger), "")
+
+ val classDoc = doc.classNamed("foo.O")
+ assertNotNull(classDoc)
+
+ val companionDoc = doc.classNamed("foo.O.Companion")
+ assertNotNull(companionDoc)
+
+ val pkgDoc = doc.packageNamed("foo")!!
+ assertEquals(2, pkgDoc.allClasses().size)
+ }
+ }
+
+}
diff --git a/core/src/test/kotlin/markdown/ParserTest.kt b/core/src/test/kotlin/markdown/ParserTest.kt
new file mode 100644
index 00000000..5a7adf05
--- /dev/null
+++ b/core/src/test/kotlin/markdown/ParserTest.kt
@@ -0,0 +1,142 @@
+package org.jetbrains.dokka.tests
+
+import org.junit.Test
+import org.jetbrains.dokka.toTestString
+import org.jetbrains.dokka.parseMarkdown
+import org.junit.Ignore
+
+@Ignore public class ParserTest {
+ fun runTestFor(text : String) {
+ println("MD: ---")
+ println(text)
+ val markdownTree = parseMarkdown(text)
+ println("AST: ---")
+ println(markdownTree.toTestString())
+ println()
+ }
+
+ @Test fun text() {
+ runTestFor("text")
+ }
+
+ @Test fun textWithSpaces() {
+ runTestFor("text and string")
+ }
+
+ @Test fun textWithColon() {
+ runTestFor("text and string: cool!")
+ }
+
+ @Test fun link() {
+ runTestFor("text [links]")
+ }
+
+ @Test fun linkWithHref() {
+ runTestFor("text [links](http://google.com)")
+ }
+
+ @Test fun multiline() {
+ runTestFor(
+ """
+text
+and
+string
+""")
+ }
+
+ @Test fun para() {
+ runTestFor(
+ """
+paragraph number
+one
+
+paragraph
+number two
+""")
+ }
+
+ @Test fun bulletList() {
+ runTestFor(
+ """* list item 1
+* list item 2
+""")
+ }
+
+ @Test fun bulletListWithLines() {
+ runTestFor(
+ """
+* list item 1
+ continue 1
+* list item 2
+ continue 2
+ """)
+ }
+
+ @Test fun bulletListStrong() {
+ runTestFor(
+ """
+* list *item* 1
+ continue 1
+* list *item* 2
+ continue 2
+ """)
+ }
+
+ @Test fun emph() {
+ runTestFor("*text*")
+ }
+
+ @Test fun directive() {
+ runTestFor("A text \${code with.another.value} with directive")
+ }
+
+ @Test fun emphAndEmptySection() {
+ runTestFor("*text*\n\$sec:\n")
+ }
+
+ @Test fun emphAndSection() {
+ runTestFor("*text*\n\$sec: some text\n")
+ }
+
+ @Test fun emphAndBracedSection() {
+ runTestFor("Text *bold* text \n\${sec}: some text")
+ }
+
+ @Test fun section() {
+ runTestFor(
+ "Plain text \n\$one: Summary \n\${two}: Description with *emphasis* \n\${An example of a section}: Example")
+ }
+
+ @Test fun anonymousSection() {
+ runTestFor("Summary\n\nDescription\n")
+ }
+
+ @Test fun specialSection() {
+ runTestFor(
+ "Plain text \n\$\$summary: Summary \n\${\$description}: Description \n\${\$An example of a section}: Example")
+ }
+
+ @Test fun emptySection() {
+ runTestFor(
+ "Plain text \n\$summary:")
+ }
+
+ val b = "$"
+ @Test fun pair() {
+ runTestFor(
+ """Represents a generic pair of two values.
+
+There is no meaning attached to values in this class, it can be used for any purpose.
+Pair exhibits value semantics, i.e. two pairs are equal if both components are equal.
+
+An example of decomposing it into values:
+${b}{code test.tuples.PairTest.pairMultiAssignment}
+
+${b}constructor: Creates new instance of [Pair]
+${b}first: First value
+${b}second: Second value""""
+ )
+ }
+
+}
+
diff --git a/core/src/test/kotlin/model/ClassTest.kt b/core/src/test/kotlin/model/ClassTest.kt
new file mode 100644
index 00000000..981791c4
--- /dev/null
+++ b/core/src/test/kotlin/model/ClassTest.kt
@@ -0,0 +1,275 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.Content
+import org.jetbrains.dokka.DocumentationNode
+import org.jetbrains.dokka.DocumentationReference
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+public class ClassTest {
+ @Test fun emptyClass() {
+ verifyModel("testdata/classes/emptyClass.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("Klass", name)
+ assertEquals(Content.Empty, content)
+ assertEquals("<init>", members.single().name)
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun emptyObject() {
+ verifyModel("testdata/classes/emptyObject.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Object, kind)
+ assertEquals("Obj", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun classWithConstructor() {
+ verifyModel("testdata/classes/classWithConstructor.kt") { model ->
+ with (model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("Klass", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(links.none())
+
+ assertEquals(1, members.count())
+ with(members.elementAt(0)) {
+ assertEquals("<init>", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Constructor, kind)
+ assertEquals(2, details.count())
+ assertEquals("public", details.elementAt(0).name)
+ with(details.elementAt(1)) {
+ assertEquals("name", name)
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", details.single().name)
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ }
+ }
+ }
+
+ @Test fun classWithFunction() {
+ verifyModel("testdata/classes/classWithFunction.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("Klass", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(links.none())
+
+ assertEquals(2, members.count())
+ with(members.elementAt(0)) {
+ assertEquals("<init>", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Constructor, kind)
+ assertEquals(1, details.count())
+ assertEquals("public", details.elementAt(0).name)
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ with(members.elementAt(1)) {
+ assertEquals("fn", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("Unit", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ }
+ }
+ }
+
+ @Test fun classWithProperty() {
+ verifyModel("testdata/classes/classWithProperty.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("Klass", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(links.none())
+
+ assertEquals(2, members.count())
+ with(members.elementAt(0)) {
+ assertEquals("<init>", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Constructor, kind)
+ assertEquals(1, details.count())
+ assertEquals("public", details.elementAt(0).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ with(members.elementAt(1)) {
+ assertEquals("name", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+ }
+
+ @Test fun classWithCompanionObject() {
+ verifyModel("testdata/classes/classWithCompanionObject.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("Klass", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(links.none())
+
+ assertEquals(3, members.count())
+ with(members.elementAt(0)) {
+ assertEquals("<init>", name)
+ assertEquals(Content.Empty, content)
+ }
+ with(members.elementAt(1)) {
+ assertEquals("x", name)
+ assertEquals(DocumentationNode.Kind.CompanionObjectProperty, kind)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ with(members.elementAt(2)) {
+ assertEquals("foo", name)
+ assertEquals(DocumentationNode.Kind.CompanionObjectFunction, kind)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+ }
+
+ @Test fun annotatedClass() {
+ verifyPackageMember("testdata/classes/annotatedClass.kt", withKotlinRuntime = true) { cls ->
+ assertEquals(1, cls.annotations.count())
+ with(cls.annotations[0]) {
+ assertEquals("Strictfp", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ }
+ }
+ }
+
+ @Test fun dataClass() {
+ verifyPackageMember("testdata/classes/dataClass.kt") { cls ->
+ val modifiers = cls.details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertTrue("data" in modifiers)
+ }
+ }
+
+ @Test fun sealedClass() {
+ verifyPackageMember("testdata/classes/sealedClass.kt") { cls ->
+ val modifiers = cls.details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertEquals(1, modifiers.count { it == "sealed" })
+ }
+ }
+
+ @Test fun annotatedClassWithAnnotationParameters() {
+ verifyModel("testdata/classes/annotatedClassWithAnnotationParameters.kt") { model ->
+ with(model.members.single().members.single()) {
+ with(deprecation!!) {
+ assertEquals("Deprecated", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Value, kind)
+ assertEquals("\"should no longer be used\"", name)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun javaAnnotationClass() {
+ verifyModel("testdata/classes/javaAnnotationClass.kt", withJdk = true) { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(1, annotations.count())
+ with(annotations[0]) {
+ assertEquals("Retention", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Value, kind)
+ assertEquals("RetentionPolicy.SOURCE", name)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun notOpenClass() {
+ verifyModel("testdata/classes/notOpenClass.kt") { model ->
+ with(model.members.single().members.first { it.name == "D"}.members.first { it.name == "f" }) {
+ val modifiers = details(DocumentationNode.Kind.Modifier)
+ assertEquals(2, modifiers.size)
+ assertEquals("final", modifiers[1].name)
+
+ val overrideReferences = references(DocumentationReference.Kind.Override)
+ assertEquals(1, overrideReferences.size)
+ }
+ }
+ }
+
+ @Test fun indirectOverride() {
+ verifyModel("testdata/classes/indirectOverride.kt") { model ->
+ with(model.members.single().members.first { it.name == "E"}.members.first { it.name == "foo" }) {
+ val modifiers = details(DocumentationNode.Kind.Modifier)
+ assertEquals(2, modifiers.size)
+ assertEquals("final", modifiers[1].name)
+
+ val overrideReferences = references(DocumentationReference.Kind.Override)
+ assertEquals(1, overrideReferences.size)
+ }
+ }
+ }
+
+ @Test fun innerClass() {
+ verifyPackageMember("testdata/classes/innerClass.kt") { cls ->
+ val innerClass = cls.members.single { it.name == "D" }
+ val modifiers = innerClass.details(DocumentationNode.Kind.Modifier)
+ assertEquals(3, modifiers.size)
+ assertEquals("inner", modifiers[2].name)
+ }
+ }
+
+ @Test fun companionObjectExtension() {
+ verifyModel("testdata/classes/companionObjectExtension.kt") { model ->
+ val pkg = model.members.single()
+ val cls = pkg.members.single { it.name == "Foo" }
+ val extensions = cls.extensions.filter { it.kind == DocumentationNode.Kind.CompanionObjectProperty }
+ assertEquals(1, extensions.size)
+ }
+ }
+
+ @Test fun secondaryConstructor() {
+ verifyPackageMember("testdata/classes/secondaryConstructor.kt") { cls ->
+ val constructors = cls.members(DocumentationNode.Kind.Constructor)
+ assertEquals(2, constructors.size)
+ with (constructors.first { it.details(DocumentationNode.Kind.Parameter).size == 1}) {
+ assertEquals("<init>", name)
+ assertEquals("This is a secondary constructor.", summary.toTestString())
+ }
+ }
+ }
+}
diff --git a/core/src/test/kotlin/model/CommentTest.kt b/core/src/test/kotlin/model/CommentTest.kt
new file mode 100644
index 00000000..f3792610
--- /dev/null
+++ b/core/src/test/kotlin/model/CommentTest.kt
@@ -0,0 +1,153 @@
+package org.jetbrains.dokka.tests
+
+import org.junit.Test
+import kotlin.test.*
+import org.jetbrains.dokka.*
+
+public class CommentTest {
+ @Test fun emptyDoc() {
+ verifyModel("testdata/comments/emptyDoc.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(Content.Empty, content)
+ }
+ }
+ }
+
+ @Test fun emptyDocButComment() {
+ verifyModel("testdata/comments/emptyDocButComment.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(Content.Empty, content)
+ }
+ }
+ }
+
+ @Test fun multilineDoc() {
+ verifyModel("testdata/comments/multilineDoc.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("doc1", content.summary.toTestString())
+ assertEquals("doc2\ndoc3", content.description.toTestString())
+ }
+ }
+ }
+
+ @Test fun multilineDocWithComment() {
+ verifyModel("testdata/comments/multilineDocWithComment.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("doc1", content.summary.toTestString())
+ assertEquals("doc2\ndoc3", content.description.toTestString())
+ }
+ }
+ }
+
+ @Test fun oneLineDoc() {
+ verifyModel("testdata/comments/oneLineDoc.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("doc", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun oneLineDocWithComment() {
+ verifyModel("testdata/comments/oneLineDocWithComment.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("doc", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun oneLineDocWithEmptyLine() {
+ verifyModel("testdata/comments/oneLineDocWithEmptyLine.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("doc", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun emptySection() {
+ verifyModel("testdata/comments/emptySection.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Summary", content.summary.toTestString())
+ assertEquals(1, content.sections.count())
+ with (content.findSectionByTag("one")!!) {
+ assertEquals("One", tag)
+ assertEquals("", toTestString())
+ }
+ }
+ }
+ }
+
+ @Test fun section1() {
+ verifyModel("testdata/comments/section1.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Summary", content.summary.toTestString())
+ assertEquals(1, content.sections.count())
+ with (content.findSectionByTag("one")!!) {
+ assertEquals("One", tag)
+ assertEquals("section one", toTestString())
+ }
+ }
+ }
+ }
+
+ @Test fun section2() {
+ verifyModel("testdata/comments/section2.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Summary", content.summary.toTestString())
+ assertEquals(2, content.sections.count())
+ with (content.findSectionByTag("one")!!) {
+ assertEquals("One", tag)
+ assertEquals("section one", toTestString())
+ }
+ with (content.findSectionByTag("two")!!) {
+ assertEquals("Two", tag)
+ assertEquals("section two", toTestString())
+ }
+ }
+ }
+ }
+
+ @Test fun multilineSection() {
+ verifyModel("testdata/comments/multilineSection.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Summary", content.summary.toTestString())
+ assertEquals(1, content.sections.count())
+ with (content.findSectionByTag("one")!!) {
+ assertEquals("One", tag)
+ assertEquals("""line one
+line two""", toTestString())
+ }
+ }
+ }
+ }
+
+ @Test fun directive() {
+ verifyModel("testdata/comments/directive.kt") { model ->
+ with(model.members.single().members.first()) {
+ assertEquals("Summary", content.summary.toTestString())
+ with (content.description) {
+ assertEqualsIgnoringSeparators("""[code]
+if (true) {
+ println(property)
+}
+[/code]
+[code]
+if (true) {
+ println(property)
+}
+[/code]
+[code]
+if (true) {
+ println(property)
+}
+[/code]
+[code]
+if (true) {
+ println(property)
+}
+[/code]
+""", toTestString())
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/test/kotlin/model/FunctionTest.kt b/core/src/test/kotlin/model/FunctionTest.kt
new file mode 100644
index 00000000..83fd8223
--- /dev/null
+++ b/core/src/test/kotlin/model/FunctionTest.kt
@@ -0,0 +1,227 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.Content
+import org.jetbrains.dokka.DocumentationNode
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+public class FunctionTest {
+ @Test fun function() {
+ verifyModel("testdata/functions/function.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("fn", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("Function fn", content.summary.toTestString())
+ assertEquals("Unit", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun functionWithReceiver() {
+ verifyModel("testdata/functions/functionWithReceiver.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("kotlin.String", name)
+ assertEquals(DocumentationNode.Kind.ExternalClass, kind)
+ assertEquals(2, members.count())
+ with(members[0]) {
+ assertEquals("fn", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("Function with receiver", content.summary.toTestString())
+ assertEquals("public", details.elementAt(0).name)
+ assertEquals("final", details.elementAt(1).name)
+ with(details.elementAt(2)) {
+ assertEquals("<this>", name)
+ assertEquals(DocumentationNode.Kind.Receiver, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", details.single().name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ assertEquals("Unit", details.elementAt(3).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ with(members[1]) {
+ assertEquals("fn", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ }
+ }
+ }
+ }
+
+ @Test fun genericFunction() {
+ verifyModel("testdata/functions/genericFunction.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("generic", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("generic function", content.summary.toTestString())
+
+ assertEquals("private", details.elementAt(0).name)
+ assertEquals("final", details.elementAt(1).name)
+ with(details.elementAt(2)) {
+ assertEquals("T", name)
+ assertEquals(DocumentationNode.Kind.TypeParameter, kind)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ assertEquals("Unit", details.elementAt(3).name)
+
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+ @Test fun genericFunctionWithConstraints() {
+ verifyModel("testdata/functions/genericFunctionWithConstraints.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("generic", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("generic function", content.summary.toTestString())
+
+ assertEquals("public", details.elementAt(0).name)
+ assertEquals("final", details.elementAt(1).name)
+ with(details.elementAt(2)) {
+ assertEquals("T", name)
+ assertEquals(DocumentationNode.Kind.TypeParameter, kind)
+ assertEquals(Content.Empty, content)
+ with(details.single()) {
+ assertEquals("R", name)
+ assertEquals(DocumentationNode.Kind.UpperBound, kind)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ with(details.elementAt(3)) {
+ assertEquals("R", name)
+ assertEquals(DocumentationNode.Kind.TypeParameter, kind)
+ assertEquals(Content.Empty, content)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ assertEquals("Unit", details.elementAt(4).name)
+
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun functionWithParams() {
+ verifyModel("testdata/functions/functionWithParams.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("function", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("Multiline", content.summary.toTestString())
+ assertEquals("""Function
+Documentation""", content.description.toTestString())
+
+ assertEquals("public", details.elementAt(0).name)
+ assertEquals("final", details.elementAt(1).name)
+ with(details.elementAt(2)) {
+ assertEquals("x", name)
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals("parameter", content.summary.toTestString())
+ assertEquals("Int", details.single().name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ assertEquals("Unit", details.elementAt(3).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun annotatedFunction() {
+ verifyPackageMember("testdata/functions/annotatedFunction.kt", withKotlinRuntime = true) { func ->
+ assertEquals(1, func.annotations.count())
+ with(func.annotations[0]) {
+ assertEquals("Strictfp", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ }
+ }
+ }
+
+ @Test fun functionWithNotDocumentedAnnotation() {
+ verifyPackageMember("testdata/functions/functionWithNotDocumentedAnnotation.kt") { func ->
+ assertEquals(0, func.annotations.count())
+ }
+ }
+
+ @Test fun inlineFunction() {
+ verifyPackageMember("testdata/functions/inlineFunction.kt") { func ->
+ val modifiers = func.details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertTrue("inline" in modifiers)
+ }
+ }
+
+ @Test fun functionWithAnnotatedParam() {
+ verifyModel("testdata/functions/functionWithAnnotatedParam.kt") { model ->
+ with(model.members.single().members.single { it.name == "function"} ) {
+ with(details.elementAt(2)) {
+ assertEquals(1, annotations.count())
+ with(annotations[0]) {
+ assertEquals("Fancy", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun functionWithNoinlineParam() {
+ verifyPackageMember("testdata/functions/functionWithNoinlineParam.kt") { func ->
+ with(func.details.elementAt(2)) {
+ val modifiers = details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertTrue("noinline" in modifiers)
+ }
+ }
+ }
+
+ @Test fun annotatedFunctionWithAnnotationParameters() {
+ verifyModel("testdata/functions/annotatedFunctionWithAnnotationParameters.kt") { model ->
+ with(model.members.single().members.single { it.name == "f"}) {
+ assertEquals(1, annotations.count())
+ with(annotations[0]) {
+ assertEquals("Fancy", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Value, kind)
+ assertEquals("1", name)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun functionWithDefaultParameter() {
+ verifyModel("testdata/functions/functionWithDefaultParameter.kt") { model ->
+ with(model.members.single().members.single()) {
+ with(details.elementAt(2)) {
+ val value = details(DocumentationNode.Kind.Value)
+ assertEquals(1, value.count())
+ with(value[0]) {
+ assertEquals("\"\"", name)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/test/kotlin/model/JavaTest.kt b/core/src/test/kotlin/model/JavaTest.kt
new file mode 100644
index 00000000..903260d3
--- /dev/null
+++ b/core/src/test/kotlin/model/JavaTest.kt
@@ -0,0 +1,197 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.DocumentationNode
+import org.jetbrains.dokka.DocumentationReference
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+public class JavaTest {
+ @Test fun function() {
+ verifyJavaPackageMember("testdata/java/member.java") { cls ->
+ assertEquals("Test", cls.name)
+ assertEquals(DocumentationNode.Kind.Class, cls.kind)
+ with(cls.members(DocumentationNode.Kind.Function).single()) {
+ assertEquals("fn", name)
+ assertEquals("Summary for Function", content.summary.toTestString().trimEnd())
+ assertEquals(3, content.sections.size)
+ with(content.sections[0]) {
+ assertEquals("Parameters", tag)
+ assertEquals("name", subjectName)
+ assertEquals("is String parameter ", toTestString())
+ }
+ with(content.sections[1]) {
+ assertEquals("Parameters", tag)
+ assertEquals("value", subjectName)
+ assertEquals("is int parameter ", toTestString())
+ }
+ with(content.sections[2]) {
+ assertEquals("Author", tag)
+ assertEquals("yole", toTestString())
+ }
+ assertEquals("Unit", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ with(details.first { it.name == "name" }) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ }
+ with(details.first { it.name == "value" }) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals("Int", detail(DocumentationNode.Kind.Type).name)
+ }
+ }
+ }
+ }
+
+ @Test fun memberWithModifiers() {
+ verifyJavaPackageMember("testdata/java/memberWithModifiers.java") { cls ->
+ val modifiers = cls.details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertTrue("abstract" in modifiers)
+ with(cls.members.single { it.name == "fn" }) {
+ assertEquals("protected", details[0].name)
+ }
+ with(cls.members.single { it.name == "openFn" }) {
+ assertEquals("open", details[1].name)
+ }
+ }
+ }
+
+ @Test fun superClass() {
+ verifyJavaPackageMember("testdata/java/superClass.java") { cls ->
+ val superTypes = cls.details(DocumentationNode.Kind.Supertype)
+ assertEquals(2, superTypes.size)
+ assertEquals("Exception", superTypes[0].name)
+ assertEquals("Cloneable", superTypes[1].name)
+ }
+ }
+
+ @Test fun arrayType() {
+ verifyJavaPackageMember("testdata/java/arrayType.java") { cls ->
+ with(cls.members(DocumentationNode.Kind.Function).single()) {
+ val type = detail(DocumentationNode.Kind.Type)
+ assertEquals("Array", type.name)
+ assertEquals("String", type.detail(DocumentationNode.Kind.Type).name)
+ with(details(DocumentationNode.Kind.Parameter).single()) {
+ val parameterType = detail(DocumentationNode.Kind.Type)
+ assertEquals("IntArray", parameterType.name)
+ }
+ }
+ }
+ }
+
+ @Test fun typeParameter() {
+ verifyJavaPackageMember("testdata/java/typeParameter.java") { cls ->
+ val typeParameters = cls.details(DocumentationNode.Kind.TypeParameter)
+ with(typeParameters.single()) {
+ assertEquals("T", name)
+ with(detail(DocumentationNode.Kind.UpperBound)) {
+ assertEquals("Comparable", name)
+ assertEquals("T", detail(DocumentationNode.Kind.Type).name)
+ }
+ }
+ with(cls.members(DocumentationNode.Kind.Function).single()) {
+ val methodTypeParameters = details(DocumentationNode.Kind.TypeParameter)
+ with(methodTypeParameters.single()) {
+ assertEquals("E", name)
+ }
+ }
+ }
+ }
+
+ @Test fun constructors() {
+ verifyJavaPackageMember("testdata/java/constructors.java") { cls ->
+ val constructors = cls.members(DocumentationNode.Kind.Constructor)
+ assertEquals(2, constructors.size)
+ with(constructors[0]) {
+ assertEquals("<init>", name)
+ }
+ }
+ }
+
+ @Test fun innerClass() {
+ verifyJavaPackageMember("testdata/java/innerClass.java") { cls ->
+ val innerClass = cls.members(DocumentationNode.Kind.Class).single()
+ assertEquals("D", innerClass.name)
+ }
+ }
+
+ @Test fun varargs() {
+ verifyJavaPackageMember("testdata/java/varargs.java") { cls ->
+ val fn = cls.members(DocumentationNode.Kind.Function).single()
+ val param = fn.detail(DocumentationNode.Kind.Parameter)
+ assertEquals("vararg", param.details(DocumentationNode.Kind.Modifier).first().name)
+ val psiType = param.detail(DocumentationNode.Kind.Type)
+ assertEquals("String", psiType.name)
+ assertTrue(psiType.details(DocumentationNode.Kind.Type).isEmpty())
+ }
+ }
+
+ @Test fun fields() {
+ verifyJavaPackageMember("testdata/java/field.java") { cls ->
+ val i = cls.members(DocumentationNode.Kind.Property).single { it.name == "i" }
+ assertEquals("Int", i.detail(DocumentationNode.Kind.Type).name)
+ assertTrue("var" in i.details(DocumentationNode.Kind.Modifier).map { it.name })
+
+ val s = cls.members(DocumentationNode.Kind.Property).single { it.name == "s" }
+ assertEquals("String", s.detail(DocumentationNode.Kind.Type).name)
+ assertFalse("var" in s.details(DocumentationNode.Kind.Modifier).map { it.name })
+ assertTrue("static" in s.details(DocumentationNode.Kind.Modifier).map { it.name })
+ }
+ }
+
+ @Test fun staticMethod() {
+ verifyJavaPackageMember("testdata/java/staticMethod.java") { cls ->
+ val m = cls.members(DocumentationNode.Kind.Function).single { it.name == "foo" }
+ assertTrue("static" in m.details(DocumentationNode.Kind.Modifier).map { it.name })
+ }
+ }
+
+ @Test fun annotatedAnnotation() {
+ verifyJavaPackageMember("testdata/java/annotatedAnnotation.java") { cls ->
+ assertEquals(1, cls.annotations.size)
+ with(cls.annotations[0]) {
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Parameter, kind)
+ assertEquals(1, details.count())
+ with(details[0]) {
+ assertEquals(DocumentationNode.Kind.Value, kind)
+ assertEquals("[AnnotationTarget.FIELD, AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER]", name)
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun deprecation() {
+ verifyJavaPackageMember("testdata/java/deprecation.java") { cls ->
+ val fn = cls.members(DocumentationNode.Kind.Function).single()
+ assertEquals("This should no longer be used", fn.deprecation!!.content.toTestString())
+ }
+ }
+
+ @Test fun javaLangObject() {
+ verifyJavaPackageMember("testdata/java/javaLangObject.java") { cls ->
+ val fn = cls.members(DocumentationNode.Kind.Function).single()
+ assertEquals("Any", fn.detail(DocumentationNode.Kind.Type).name)
+ }
+ }
+
+ @Test fun enumValues() {
+ verifyJavaPackageMember("testdata/java/enumValues.java") { cls ->
+ val superTypes = cls.details(DocumentationNode.Kind.Supertype)
+ assertEquals(0, superTypes.size)
+ assertEquals(1, cls.members(DocumentationNode.Kind.EnumItem).size)
+ }
+ }
+
+ @Test fun inheritorLinks() {
+ verifyJavaPackageMember("testdata/java/inheritorLinks.java") { cls ->
+ val fooClass = cls.members.single { it.name == "Foo" }
+ val inheritors = fooClass.references(DocumentationReference.Kind.Inheritor)
+ assertEquals(1, inheritors.size)
+ }
+ }
+}
diff --git a/core/src/test/kotlin/model/KotlinAsJavaTest.kt b/core/src/test/kotlin/model/KotlinAsJavaTest.kt
new file mode 100644
index 00000000..b439d399
--- /dev/null
+++ b/core/src/test/kotlin/model/KotlinAsJavaTest.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.DocumentationModule
+import org.jetbrains.dokka.DocumentationNode
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class KotlinAsJavaTest {
+ @Test fun function() {
+ verifyModelAsJava("testdata/functions/function.kt") { model ->
+ val pkg = model.members.single()
+
+ val facadeClass = pkg.members.single { it.name == "FunctionKt" }
+ assertEquals(DocumentationNode.Kind.Class, facadeClass.kind)
+
+ val fn = facadeClass.members.single()
+ assertEquals("fn", fn.name)
+ assertEquals(DocumentationNode.Kind.Function, fn.kind)
+ }
+ }
+
+ @Test fun propertyWithComment() {
+ verifyModelAsJava("testdata/comments/oneLineDoc.kt") { model ->
+ val facadeClass = model.members.single().members.single { it.name == "OneLineDocKt" }
+ val getter = facadeClass.members.single { it.name == "getProperty" }
+ assertEquals(DocumentationNode.Kind.Function, getter.kind)
+ assertEquals("doc", getter.content.summary.toTestString())
+ }
+ }
+}
+
+fun verifyModelAsJava(source: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ verifier: (DocumentationModule) -> Unit) {
+ verifyModel(source,
+ withJdk = withJdk, withKotlinRuntime = withKotlinRuntime,
+ format = "html-as-java",
+ verifier = verifier)
+}
diff --git a/core/src/test/kotlin/model/LinkTest.kt b/core/src/test/kotlin/model/LinkTest.kt
new file mode 100644
index 00000000..c30e1c10
--- /dev/null
+++ b/core/src/test/kotlin/model/LinkTest.kt
@@ -0,0 +1,48 @@
+package org.jetbrains.dokka.tests
+
+import org.junit.Test
+import kotlin.test.*
+import org.jetbrains.dokka.*
+
+public class LinkTest {
+ @Test fun linkToSelf() {
+ verifyModel("testdata/links/linkToSelf.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Foo", name)
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("This is link to [Foo -> Class:Foo]", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun linkToMember() {
+ verifyModel("testdata/links/linkToMember.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Foo", name)
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("This is link to [member -> Function:member]", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun linkToQualifiedMember() {
+ verifyModel("testdata/links/linkToQualifiedMember.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Foo", name)
+ assertEquals(DocumentationNode.Kind.Class, kind)
+ assertEquals("This is link to [Foo.member -> Function:member]", content.summary.toTestString())
+ }
+ }
+ }
+
+ @Test fun linkToParam() {
+ verifyModel("testdata/links/linkToParam.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("Foo", name)
+ assertEquals(DocumentationNode.Kind.Function, kind)
+ assertEquals("This is link to [param -> Parameter:param]", content.summary.toTestString())
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/src/test/kotlin/model/PackageTest.kt b/core/src/test/kotlin/model/PackageTest.kt
new file mode 100644
index 00000000..aa5a059a
--- /dev/null
+++ b/core/src/test/kotlin/model/PackageTest.kt
@@ -0,0 +1,86 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.Content
+import org.jetbrains.dokka.DocumentationNode
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+public class PackageTest {
+ @Test fun rootPackage() {
+ verifyModel("testdata/packages/rootPackage.kt") { model ->
+ with(model.members.single()) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals("", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun simpleNamePackage() {
+ verifyModel("testdata/packages/simpleNamePackage.kt") { model ->
+ with(model.members.single()) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals("simple", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun dottedNamePackage() {
+ verifyModel("testdata/packages/dottedNamePackage.kt") { model ->
+ with(model.members.single()) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals("dot.name", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun multipleFiles() {
+ verifyModel(KotlinSourceRoot("testdata/packages/dottedNamePackage.kt"),
+ KotlinSourceRoot("testdata/packages/simpleNamePackage.kt")) { model ->
+ assertEquals(2, model.members.count())
+ with(model.members.single { it.name == "simple" }) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals("simple", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ with(model.members.single { it.name == "dot.name" }) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun multipleFilesSamePackage() {
+ verifyModel(KotlinSourceRoot("testdata/packages/simpleNamePackage.kt"),
+ KotlinSourceRoot("testdata/packages/simpleNamePackage2.kt")) { model ->
+ assertEquals(1, model.members.count())
+ with(model.members.elementAt(0)) {
+ assertEquals(DocumentationNode.Kind.Package, kind)
+ assertEquals("simple", name)
+ assertEquals(Content.Empty, content)
+ assertTrue(details.none())
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/test/kotlin/model/PropertyTest.kt b/core/src/test/kotlin/model/PropertyTest.kt
new file mode 100644
index 00000000..716aac54
--- /dev/null
+++ b/core/src/test/kotlin/model/PropertyTest.kt
@@ -0,0 +1,103 @@
+package org.jetbrains.dokka.tests
+
+import org.jetbrains.dokka.Content
+import org.jetbrains.dokka.DocumentationNode
+import org.jetbrains.dokka.DocumentationReference
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+public class PropertyTest {
+ @Test fun valueProperty() {
+ verifyModel("testdata/properties/valueProperty.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("property", name)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun variableProperty() {
+ verifyModel("testdata/properties/variableProperty.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("property", name)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(members.none())
+ assertTrue(links.none())
+ }
+ }
+ }
+
+ @Test fun valuePropertyWithGetter() {
+ verifyModel("testdata/properties/valuePropertyWithGetter.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("property", name)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ }
+ }
+
+ @Test fun variablePropertyWithAccessors() {
+ verifyModel("testdata/properties/variablePropertyWithAccessors.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("property", name)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ assertEquals(Content.Empty, content)
+ assertEquals("String", detail(DocumentationNode.Kind.Type).name)
+ val modifiers = details(DocumentationNode.Kind.Modifier).map { it.name }
+ assertTrue("final" in modifiers)
+ assertTrue("public" in modifiers)
+ assertTrue("var" in modifiers)
+ assertTrue(links.none())
+ assertTrue(members.none())
+ }
+ }
+ }
+
+ @Test fun annotatedProperty() {
+ verifyModel("testdata/properties/annotatedProperty.kt", withKotlinRuntime = true) { model ->
+ with(model.members.single().members.single()) {
+ assertEquals(1, annotations.count())
+ with(annotations[0]) {
+ assertEquals("Volatile", name)
+ assertEquals(Content.Empty, content)
+ assertEquals(DocumentationNode.Kind.Annotation, kind)
+ }
+ }
+ }
+ }
+
+ @Test fun propertyWithReceiver() {
+ verifyModel("testdata/properties/propertyWithReceiver.kt") { model ->
+ with(model.members.single().members.single()) {
+ assertEquals("kotlin.String", name)
+ assertEquals(DocumentationNode.Kind.ExternalClass, kind)
+ with(members.single()) {
+ assertEquals("foobar", name)
+ assertEquals(DocumentationNode.Kind.Property, kind)
+ }
+ }
+ }
+ }
+
+ @Test fun propertyOverride() {
+ verifyModel("testdata/properties/propertyOverride.kt") { model ->
+ with(model.members.single().members.single { it.name == "Bar" }.members.single { it.name == "xyzzy"}) {
+ assertEquals("xyzzy", name)
+ val override = references(DocumentationReference.Kind.Override).single().to
+ assertEquals("xyzzy", override.name)
+ assertEquals("Foo", override.owner!!.name)
+ }
+ }
+ }
+}
diff --git a/core/testdata/classes/annotatedClass.kt b/core/testdata/classes/annotatedClass.kt
new file mode 100644
index 00000000..1b58f56c
--- /dev/null
+++ b/core/testdata/classes/annotatedClass.kt
@@ -0,0 +1 @@
+@Strictfp class Foo() {}
diff --git a/core/testdata/classes/annotatedClassWithAnnotationParameters.kt b/core/testdata/classes/annotatedClassWithAnnotationParameters.kt
new file mode 100644
index 00000000..930d6a62
--- /dev/null
+++ b/core/testdata/classes/annotatedClassWithAnnotationParameters.kt
@@ -0,0 +1 @@
+@Deprecated("should no longer be used") class Foo() {}
diff --git a/core/testdata/classes/classWithCompanionObject.kt b/core/testdata/classes/classWithCompanionObject.kt
new file mode 100644
index 00000000..fdbd915d
--- /dev/null
+++ b/core/testdata/classes/classWithCompanionObject.kt
@@ -0,0 +1,7 @@
+class Klass() {
+ companion object {
+ val x = 1
+
+ fun foo() {}
+ }
+}
diff --git a/core/testdata/classes/classWithConstructor.kt b/core/testdata/classes/classWithConstructor.kt
new file mode 100644
index 00000000..0751d570
--- /dev/null
+++ b/core/testdata/classes/classWithConstructor.kt
@@ -0,0 +1 @@
+class Klass(name: String) \ No newline at end of file
diff --git a/core/testdata/classes/classWithFunction.kt b/core/testdata/classes/classWithFunction.kt
new file mode 100644
index 00000000..a981cfb6
--- /dev/null
+++ b/core/testdata/classes/classWithFunction.kt
@@ -0,0 +1,4 @@
+class Klass {
+ fun fn() {
+ }
+}
diff --git a/core/testdata/classes/classWithProperty.kt b/core/testdata/classes/classWithProperty.kt
new file mode 100644
index 00000000..2a849572
--- /dev/null
+++ b/core/testdata/classes/classWithProperty.kt
@@ -0,0 +1,3 @@
+class Klass {
+ val name: String = ""
+} \ No newline at end of file
diff --git a/core/testdata/classes/companionObjectExtension.kt b/core/testdata/classes/companionObjectExtension.kt
new file mode 100644
index 00000000..4b471376
--- /dev/null
+++ b/core/testdata/classes/companionObjectExtension.kt
@@ -0,0 +1,10 @@
+class Foo {
+ companion object Default {
+ }
+}
+
+
+/**
+ * The def
+ */
+val Foo.Default.x: Int get() = 1
diff --git a/core/testdata/classes/dataClass.kt b/core/testdata/classes/dataClass.kt
new file mode 100644
index 00000000..62c6f0ec
--- /dev/null
+++ b/core/testdata/classes/dataClass.kt
@@ -0,0 +1 @@
+data class Foo() {}
diff --git a/core/testdata/classes/emptyClass.kt b/core/testdata/classes/emptyClass.kt
new file mode 100644
index 00000000..abd20cc8
--- /dev/null
+++ b/core/testdata/classes/emptyClass.kt
@@ -0,0 +1,3 @@
+class Klass {
+
+} \ No newline at end of file
diff --git a/core/testdata/classes/emptyObject.kt b/core/testdata/classes/emptyObject.kt
new file mode 100644
index 00000000..4138bf31
--- /dev/null
+++ b/core/testdata/classes/emptyObject.kt
@@ -0,0 +1,3 @@
+object Obj {
+
+} \ No newline at end of file
diff --git a/core/testdata/classes/genericClass.kt b/core/testdata/classes/genericClass.kt
new file mode 100644
index 00000000..db20ff7e
--- /dev/null
+++ b/core/testdata/classes/genericClass.kt
@@ -0,0 +1,3 @@
+class Klass<T> {
+
+} \ No newline at end of file
diff --git a/core/testdata/classes/indirectOverride.kt b/core/testdata/classes/indirectOverride.kt
new file mode 100644
index 00000000..8d091b80
--- /dev/null
+++ b/core/testdata/classes/indirectOverride.kt
@@ -0,0 +1,9 @@
+abstract class C() {
+ abstract fun foo()
+}
+
+abstract class D(): C()
+
+class E(): D() {
+ override fun foo() {}
+}
diff --git a/core/testdata/classes/innerClass.kt b/core/testdata/classes/innerClass.kt
new file mode 100644
index 00000000..3c6e497d
--- /dev/null
+++ b/core/testdata/classes/innerClass.kt
@@ -0,0 +1,5 @@
+class C {
+ inner class D {
+
+ }
+} \ No newline at end of file
diff --git a/core/testdata/classes/javaAnnotationClass.kt b/core/testdata/classes/javaAnnotationClass.kt
new file mode 100644
index 00000000..95600147
--- /dev/null
+++ b/core/testdata/classes/javaAnnotationClass.kt
@@ -0,0 +1,5 @@
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+@Retention(RetentionPolicy.SOURCE)
+public annotation class throws()
diff --git a/core/testdata/classes/notOpenClass.kt b/core/testdata/classes/notOpenClass.kt
new file mode 100644
index 00000000..edee2c1a
--- /dev/null
+++ b/core/testdata/classes/notOpenClass.kt
@@ -0,0 +1,7 @@
+open class C() {
+ open fun f() {}
+}
+
+class D() : C() {
+ override fun f() {}
+}
diff --git a/core/testdata/classes/sealedClass.kt b/core/testdata/classes/sealedClass.kt
new file mode 100644
index 00000000..93350393
--- /dev/null
+++ b/core/testdata/classes/sealedClass.kt
@@ -0,0 +1 @@
+sealed class Foo() {}
diff --git a/core/testdata/classes/secondaryConstructor.kt b/core/testdata/classes/secondaryConstructor.kt
new file mode 100644
index 00000000..e5cb2557
--- /dev/null
+++ b/core/testdata/classes/secondaryConstructor.kt
@@ -0,0 +1,5 @@
+class C() {
+ /** This is a secondary constructor. */
+ constructor(s: String): this() {
+ }
+}
diff --git a/core/testdata/comments/directive.kt b/core/testdata/comments/directive.kt
new file mode 100644
index 00000000..b27f5a48
--- /dev/null
+++ b/core/testdata/comments/directive.kt
@@ -0,0 +1,35 @@
+/**
+ * Summary
+ *
+ * @sample example1
+ * @sample example2
+ * @sample X.example3
+ * @sample X.Y.example4
+ */
+val property = "test"
+
+fun example1(node: String) = if (true) {
+ println(property)
+}
+
+fun example2(node: String) {
+ if (true) {
+ println(property)
+ }
+}
+
+class X {
+ fun example3(node: String) {
+ if (true) {
+ println(property)
+ }
+ }
+
+ class Y {
+ fun example4(node: String) {
+ if (true) {
+ println(property)
+ }
+ }
+ }
+}
diff --git a/core/testdata/comments/emptyDoc.kt b/core/testdata/comments/emptyDoc.kt
new file mode 100644
index 00000000..b87cce57
--- /dev/null
+++ b/core/testdata/comments/emptyDoc.kt
@@ -0,0 +1 @@
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/emptyDocButComment.kt b/core/testdata/comments/emptyDocButComment.kt
new file mode 100644
index 00000000..ceb24753
--- /dev/null
+++ b/core/testdata/comments/emptyDocButComment.kt
@@ -0,0 +1,2 @@
+/* comment */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/emptySection.kt b/core/testdata/comments/emptySection.kt
new file mode 100644
index 00000000..47d6b1a5
--- /dev/null
+++ b/core/testdata/comments/emptySection.kt
@@ -0,0 +1,6 @@
+
+/**
+ * Summary
+ * @one
+ */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/multilineDoc.kt b/core/testdata/comments/multilineDoc.kt
new file mode 100644
index 00000000..31cfa3a7
--- /dev/null
+++ b/core/testdata/comments/multilineDoc.kt
@@ -0,0 +1,7 @@
+/**
+ * doc1
+ *
+ * doc2
+ * doc3
+ */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/multilineDocWithComment.kt b/core/testdata/comments/multilineDocWithComment.kt
new file mode 100644
index 00000000..88d22642
--- /dev/null
+++ b/core/testdata/comments/multilineDocWithComment.kt
@@ -0,0 +1,8 @@
+/**
+ * doc1
+ *
+ * doc2
+ * doc3
+ */
+// comment
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/multilineSection.kt b/core/testdata/comments/multilineSection.kt
new file mode 100644
index 00000000..6ef4df2c
--- /dev/null
+++ b/core/testdata/comments/multilineSection.kt
@@ -0,0 +1,7 @@
+/**
+ * Summary
+ * @one
+ * line one
+ * line two
+ */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/oneLineDoc.kt b/core/testdata/comments/oneLineDoc.kt
new file mode 100644
index 00000000..92a40c67
--- /dev/null
+++ b/core/testdata/comments/oneLineDoc.kt
@@ -0,0 +1,2 @@
+/** doc */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/oneLineDocWithComment.kt b/core/testdata/comments/oneLineDocWithComment.kt
new file mode 100644
index 00000000..c8467933
--- /dev/null
+++ b/core/testdata/comments/oneLineDocWithComment.kt
@@ -0,0 +1,3 @@
+/** doc */
+// comment
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/oneLineDocWithEmptyLine.kt b/core/testdata/comments/oneLineDocWithEmptyLine.kt
new file mode 100644
index 00000000..e364c416
--- /dev/null
+++ b/core/testdata/comments/oneLineDocWithEmptyLine.kt
@@ -0,0 +1,3 @@
+/** doc */
+
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/section1.kt b/core/testdata/comments/section1.kt
new file mode 100644
index 00000000..7c763b4c
--- /dev/null
+++ b/core/testdata/comments/section1.kt
@@ -0,0 +1,5 @@
+/**
+ * Summary
+ * @one section one
+ */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/comments/section2.kt b/core/testdata/comments/section2.kt
new file mode 100644
index 00000000..e280793e
--- /dev/null
+++ b/core/testdata/comments/section2.kt
@@ -0,0 +1,6 @@
+/**
+ * Summary
+ * @one section one
+ * @two section two
+ */
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/format/accessor.kt b/core/testdata/format/accessor.kt
new file mode 100644
index 00000000..5a4d1742
--- /dev/null
+++ b/core/testdata/format/accessor.kt
@@ -0,0 +1,5 @@
+class C() {
+ var x: String
+ /** The getter returns an empty string. */ get() = ""
+ /** The setter does nothing. */ set(value) { }
+}
diff --git a/core/testdata/format/accessor.md b/core/testdata/format/accessor.md
new file mode 100644
index 00000000..8279f452
--- /dev/null
+++ b/core/testdata/format/accessor.md
@@ -0,0 +1,18 @@
+[test](test/index) / [C](test/-c/index) / [x](test/-c/x)
+
+
+# x
+
+`var x: String`
+**Getter**
+
+The getter returns an empty string.
+
+
+**Setter**
+
+The setter does nothing.
+
+
+
+
diff --git a/core/testdata/format/annotatedTypeParameter.kt b/core/testdata/format/annotatedTypeParameter.kt
new file mode 100644
index 00000000..cc3bfc1a
--- /dev/null
+++ b/core/testdata/format/annotatedTypeParameter.kt
@@ -0,0 +1,2 @@
+public fun <E> containsAll(elements: Collection<@UnsafeVariance E>): @UnsafeVariance E {
+}
diff --git a/core/testdata/format/annotatedTypeParameter.md b/core/testdata/format/annotatedTypeParameter.md
new file mode 100644
index 00000000..7d26a162
--- /dev/null
+++ b/core/testdata/format/annotatedTypeParameter.md
@@ -0,0 +1,8 @@
+[test](test/index) / [containsAll](test/contains-all)
+
+
+# containsAll
+
+`fun &lt;E&gt; containsAll(elements:&nbsp;Collection&lt;@UnsafeVariance E&gt;): @UnsafeVariance E`
+
+
diff --git a/core/testdata/format/annotationClass.kt b/core/testdata/format/annotationClass.kt
new file mode 100644
index 00000000..89d494fb
--- /dev/null
+++ b/core/testdata/format/annotationClass.kt
@@ -0,0 +1 @@
+annotation class fancy
diff --git a/core/testdata/format/annotationClass.md b/core/testdata/format/annotationClass.md
new file mode 100644
index 00000000..03548aa3
--- /dev/null
+++ b/core/testdata/format/annotationClass.md
@@ -0,0 +1,14 @@
+[test](test/index) / [fancy](test/fancy/index)
+
+
+# fancy
+
+`annotation class fancy`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/fancy/-init-) | `fancy()` |
+
diff --git a/core/testdata/format/annotationParams.kt b/core/testdata/format/annotationParams.kt
new file mode 100644
index 00000000..f259a740
--- /dev/null
+++ b/core/testdata/format/annotationParams.kt
@@ -0,0 +1 @@
+@JvmName("FFF") fun f() {}
diff --git a/core/testdata/format/annotationParams.md b/core/testdata/format/annotationParams.md
new file mode 100644
index 00000000..8cdd6e96
--- /dev/null
+++ b/core/testdata/format/annotationParams.md
@@ -0,0 +1,8 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`@JvmName("FFF") fun f(): Unit`
+
+
diff --git a/core/testdata/format/annotations.kt b/core/testdata/format/annotations.kt
new file mode 100644
index 00000000..57f76249
--- /dev/null
+++ b/core/testdata/format/annotations.kt
@@ -0,0 +1,6 @@
+data class Foo {
+ inline fun bar(noinline notInlined: () -> Unit) {
+ }
+
+ inline val x: Int
+}
diff --git a/core/testdata/format/annotations.md b/core/testdata/format/annotations.md
new file mode 100644
index 00000000..b898d55c
--- /dev/null
+++ b/core/testdata/format/annotations.md
@@ -0,0 +1,26 @@
+[test](test/index) / [Foo](test/-foo/index)
+
+
+# Foo
+
+`data class Foo`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-foo/-init-) | `Foo()` |
+
+
+### Properties
+
+
+| [x](test/-foo/x) | `val x: Int` |
+
+
+### Functions
+
+
+| [bar](test/-foo/bar) | `fun bar(notInlined:&nbsp;()&nbsp;-&gt;&nbsp;Unit): Unit` |
+
diff --git a/core/testdata/format/bracket.html b/core/testdata/format/bracket.html
new file mode 100644
index 00000000..5630073d
--- /dev/null
+++ b/core/testdata/format/bracket.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/foo">foo</a><br/>
+<br/>
+<h1>foo</h1>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>bar[]</p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/bracket.kt b/core/testdata/format/bracket.kt
new file mode 100644
index 00000000..d41b0073
--- /dev/null
+++ b/core/testdata/format/bracket.kt
@@ -0,0 +1,4 @@
+/**
+ * bar[]
+ */
+fun foo() {}
diff --git a/core/testdata/format/brokenLink.html b/core/testdata/format/brokenLink.html
new file mode 100644
index 00000000..32135787
--- /dev/null
+++ b/core/testdata/format/brokenLink.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / f</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/f">f</a><br/>
+<br/>
+<h1>f</h1>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>This references <a href="#">noSuchIdentifier</a>.</p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/brokenLink.kt b/core/testdata/format/brokenLink.kt
new file mode 100644
index 00000000..268a986e
--- /dev/null
+++ b/core/testdata/format/brokenLink.kt
@@ -0,0 +1,4 @@
+/**
+ * This references [noSuchIdentifier].
+ */
+fun f() { }
diff --git a/core/testdata/format/classWithCompanionObject.html b/core/testdata/format/classWithCompanionObject.html
new file mode 100644
index 00000000..be9247ea
--- /dev/null
+++ b/core/testdata/format/classWithCompanionObject.html
@@ -0,0 +1,46 @@
+<HTML>
+<HEAD>
+<title>test / Klass</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-klass/index">Klass</a><br/>
+<br/>
+<h1>Klass</h1>
+<code><span class="keyword">class </span><span class="identifier">Klass</span></code><br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-klass/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Klass</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Companion Object Properties</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-klass/x">x</a></td>
+<td>
+<code><span class="keyword">val </span><span class="identifier">x</span><span class="symbol">: </span><span class="identifier">Int</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Companion Object Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-klass/foo">foo</a></td>
+<td>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/classWithCompanionObject.kt b/core/testdata/format/classWithCompanionObject.kt
new file mode 100644
index 00000000..fdbd915d
--- /dev/null
+++ b/core/testdata/format/classWithCompanionObject.kt
@@ -0,0 +1,7 @@
+class Klass() {
+ companion object {
+ val x = 1
+
+ fun foo() {}
+ }
+}
diff --git a/core/testdata/format/classWithCompanionObject.md b/core/testdata/format/classWithCompanionObject.md
new file mode 100644
index 00000000..9398c3d3
--- /dev/null
+++ b/core/testdata/format/classWithCompanionObject.md
@@ -0,0 +1,26 @@
+[test](test/index) / [Klass](test/-klass/index)
+
+
+# Klass
+
+`class Klass`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-klass/-init-) | `Klass()` |
+
+
+### Companion Object Properties
+
+
+| [x](test/-klass/x) | `val x: Int` |
+
+
+### Companion Object Functions
+
+
+| [foo](test/-klass/foo) | `fun foo(): Unit` |
+
diff --git a/core/testdata/format/codeSpan.html b/core/testdata/format/codeSpan.html
new file mode 100644
index 00000000..cc553043
--- /dev/null
+++ b/core/testdata/format/codeSpan.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/foo">foo</a><br/>
+<br/>
+<h1>foo</h1>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>This is a <code>code span</code>.</p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/codeSpan.kt b/core/testdata/format/codeSpan.kt
new file mode 100644
index 00000000..645f454a
--- /dev/null
+++ b/core/testdata/format/codeSpan.kt
@@ -0,0 +1,4 @@
+/**
+ * This is a `code span`.
+ */
+fun foo() {} \ No newline at end of file
diff --git a/core/testdata/format/companionObjectExtension.kt b/core/testdata/format/companionObjectExtension.kt
new file mode 100644
index 00000000..f452de2c
--- /dev/null
+++ b/core/testdata/format/companionObjectExtension.kt
@@ -0,0 +1,10 @@
+class Foo {
+ companion object Default {
+ }
+}
+
+
+/**
+ * The default object property.
+ */
+val Foo.Default.x: Int get() = 1
diff --git a/core/testdata/format/companionObjectExtension.md b/core/testdata/format/companionObjectExtension.md
new file mode 100644
index 00000000..271da5b0
--- /dev/null
+++ b/core/testdata/format/companionObjectExtension.md
@@ -0,0 +1,23 @@
+[test](test/index) / [Foo](test/-foo/index)
+
+
+# Foo
+
+`class Foo`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-foo/-init-) | `Foo()` |
+
+
+### Companion Object Extension Properties
+
+
+| [x](test/x) | `val Foo.Default.x: Int`
+The default object property.
+
+ |
+
diff --git a/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.html b/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.html
new file mode 100644
index 00000000..e7b139d8
--- /dev/null
+++ b/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.html
@@ -0,0 +1,37 @@
+<HTML>
+<HEAD>
+<title>test / test.Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/test/index">test</a>&nbsp;/&nbsp;<a href="test/test/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">class </span><span class="identifier">Bar</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="test/test/-foo/index"><span class="identifier">Foo</span></a></code><br/>
+<p>See <a href="test/test/-foo/xyzzy">xyzzy</a></p>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/test/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code><p>See <a href="test/test/-foo/xyzzy">xyzzy</a></p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3>Inherited Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/test/-foo/xyzzy">xyzzy</a></td>
+<td>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">xyzzy</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt b/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt
new file mode 100644
index 00000000..102782f9
--- /dev/null
+++ b/core/testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt
@@ -0,0 +1,6 @@
+package test
+
+/**
+ * See [xyzzy]
+ */
+class Bar(): Foo()
diff --git a/core/testdata/format/crossLanguage/kotlinExtendsJava/test/Foo.java b/core/testdata/format/crossLanguage/kotlinExtendsJava/test/Foo.java
new file mode 100644
index 00000000..7c143030
--- /dev/null
+++ b/core/testdata/format/crossLanguage/kotlinExtendsJava/test/Foo.java
@@ -0,0 +1,6 @@
+package test;
+
+public class Foo {
+ public void xyzzy() {
+ }
+}
diff --git a/core/testdata/format/deprecated.class.html b/core/testdata/format/deprecated.class.html
new file mode 100644
index 00000000..69da63a1
--- /dev/null
+++ b/core/testdata/format/deprecated.class.html
@@ -0,0 +1,41 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-c/index">C</a><br/>
+<br/>
+<h1>C</h1>
+<code><span class="keyword">class </span><s><span class="identifier">C</span></s></code><br/>
+<strong>Deprecated:</strong> This class sucks<br/>
+<br/>
+<br/>
+<br/>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/f">f</a><br/>
+<br/>
+<h1>f</h1>
+<code><span class="keyword">fun </span><s><span class="identifier">f</span></s><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<strong>Deprecated:</strong> This function sucks<br/>
+<br/>
+<br/>
+<br/>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/p">p</a><br/>
+<br/>
+<h1>p</h1>
+<code><span class="keyword">val </span><s><span class="identifier">p</span></s><span class="symbol">: </span><span class="identifier">Int</span></code><br/>
+<strong>Deprecated:</strong> This property sucks<br/>
+<br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-c/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">C</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/deprecated.kt b/core/testdata/format/deprecated.kt
new file mode 100644
index 00000000..4fc568c2
--- /dev/null
+++ b/core/testdata/format/deprecated.kt
@@ -0,0 +1,5 @@
+@Deprecated("This class sucks") class C() { }
+
+@Deprecated("This function sucks") fun f() { }
+
+@Deprecated("This property sucks") val p: Int get() = 0
diff --git a/core/testdata/format/deprecated.package.html b/core/testdata/format/deprecated.package.html
new file mode 100644
index 00000000..dd80471c
--- /dev/null
+++ b/core/testdata/format/deprecated.package.html
@@ -0,0 +1,43 @@
+<HTML>
+<HEAD>
+<title>test / root package</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a><br/>
+<br/>
+<h2>Package </h2>
+<h3>Types</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-c/index">C</a></td>
+<td>
+<code><span class="keyword">class </span><s><span class="identifier">C</span></s></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Properties</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/p">p</a></td>
+<td>
+<code><span class="keyword">val </span><s><span class="identifier">p</span></s><span class="symbol">: </span><span class="identifier">Int</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/f">f</a></td>
+<td>
+<code><span class="keyword">fun </span><s><span class="identifier">f</span></s><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/emptyDescription.kt b/core/testdata/format/emptyDescription.kt
new file mode 100644
index 00000000..3ed81dfa
--- /dev/null
+++ b/core/testdata/format/emptyDescription.kt
@@ -0,0 +1,5 @@
+/**
+ * Function fn
+ */
+fun fn() {
+} \ No newline at end of file
diff --git a/core/testdata/format/emptyDescription.md b/core/testdata/format/emptyDescription.md
new file mode 100644
index 00000000..e82e7228
--- /dev/null
+++ b/core/testdata/format/emptyDescription.md
@@ -0,0 +1,11 @@
+[test](test/index) / [fn](test/fn)
+
+
+# fn
+
+`fun fn(): Unit`
+
+Function fn
+
+
+
diff --git a/core/testdata/format/entity.html b/core/testdata/format/entity.html
new file mode 100644
index 00000000..a939faad
--- /dev/null
+++ b/core/testdata/format/entity.html
@@ -0,0 +1,26 @@
+<HTML>
+<HEAD>
+<title>test / Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">class </span><span class="identifier">Bar</span></code><br/>
+<p>Copyright &copy; JetBrains 2015 &#x22;</p>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code><p>Copyright &copy; JetBrains 2015 &#x22;</p>
+</td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/entity.kt b/core/testdata/format/entity.kt
new file mode 100644
index 00000000..163d2ee6
--- /dev/null
+++ b/core/testdata/format/entity.kt
@@ -0,0 +1,4 @@
+/**
+ * Copyright &copy; JetBrains 2015 &#x22;
+ */
+class Bar {}
diff --git a/core/testdata/format/enumClass.kt b/core/testdata/format/enumClass.kt
new file mode 100644
index 00000000..c1af69d8
--- /dev/null
+++ b/core/testdata/format/enumClass.kt
@@ -0,0 +1,4 @@
+public enum class InlineOption {
+ LOCAL_CONTINUE_AND_BREAK,
+ ONLY_LOCAL_RETURN
+}
diff --git a/core/testdata/format/enumClass.md b/core/testdata/format/enumClass.md
new file mode 100644
index 00000000..7a0e03ff
--- /dev/null
+++ b/core/testdata/format/enumClass.md
@@ -0,0 +1,15 @@
+[test](test/index) / [InlineOption](test/-inline-option/index)
+
+
+# InlineOption
+
+`enum class InlineOption`
+
+
+
+### Enum Values
+
+
+| [LOCAL_CONTINUE_AND_BREAK](test/-inline-option/-l-o-c-a-l_-c-o-n-t-i-n-u-e_-a-n-d_-b-r-e-a-k) | |
+| [ONLY_LOCAL_RETURN](test/-inline-option/-o-n-l-y_-l-o-c-a-l_-r-e-t-u-r-n) | |
+
diff --git a/core/testdata/format/enumClass.value.md b/core/testdata/format/enumClass.value.md
new file mode 100644
index 00000000..bcb685d1
--- /dev/null
+++ b/core/testdata/format/enumClass.value.md
@@ -0,0 +1,8 @@
+[test](test/index) / [InlineOption](test/-inline-option/index) / [LOCAL_CONTINUE_AND_BREAK](test/-inline-option/-l-o-c-a-l_-c-o-n-t-i-n-u-e_-a-n-d_-b-r-e-a-k)
+
+
+# LOCAL_CONTINUE_AND_BREAK
+
+`LOCAL_CONTINUE_AND_BREAK`
+
+
diff --git a/core/testdata/format/extensionFunctionParameter.kt b/core/testdata/format/extensionFunctionParameter.kt
new file mode 100644
index 00000000..bfb344b9
--- /dev/null
+++ b/core/testdata/format/extensionFunctionParameter.kt
@@ -0,0 +1 @@
+public inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
diff --git a/core/testdata/format/extensionFunctionParameter.md b/core/testdata/format/extensionFunctionParameter.md
new file mode 100644
index 00000000..6980912d
--- /dev/null
+++ b/core/testdata/format/extensionFunctionParameter.md
@@ -0,0 +1,8 @@
+[test](test/index) / [apply](test/apply)
+
+
+# apply
+
+`inline fun &lt;T&gt; T.apply(f:&nbsp;T.()&nbsp;-&gt;&nbsp;Unit): T`
+
+
diff --git a/core/testdata/format/extensions.class.md b/core/testdata/format/extensions.class.md
new file mode 100644
index 00000000..c66cdb01
--- /dev/null
+++ b/core/testdata/format/extensions.class.md
@@ -0,0 +1,16 @@
+[test](test/index) / [foo](test/foo/index) / [kotlin.String](test/foo/kotlin.-string/index)
+
+
+### Extensions for kotlin.String
+
+
+| [fn](test/foo/kotlin.-string/fn) | `fun String.fn(): Unit`
+`fun String.fn(x:&nbsp;Int): Unit`
+Function with receiver
+
+ |
+| [foobar](test/foo/kotlin.-string/foobar) | `val String.foobar: Int`
+Property with receiver.
+
+ |
+
diff --git a/core/testdata/format/extensions.kt b/core/testdata/format/extensions.kt
new file mode 100644
index 00000000..6f2eff9d
--- /dev/null
+++ b/core/testdata/format/extensions.kt
@@ -0,0 +1,19 @@
+package foo
+
+/**
+ * Function with receiver
+ */
+fun String.fn() {
+}
+
+/**
+ * Function with receiver
+ */
+fun String.fn(x: Int) {
+}
+
+/**
+ * Property with receiver.
+ */
+val String.foobar: Int
+ get() = size() * 2
diff --git a/core/testdata/format/extensions.package.md b/core/testdata/format/extensions.package.md
new file mode 100644
index 00000000..76ff3676
--- /dev/null
+++ b/core/testdata/format/extensions.package.md
@@ -0,0 +1,11 @@
+[test](test/index) / [foo](test/foo/index)
+
+
+## Package foo
+
+
+### Extensions for External Classes
+
+
+| [kotlin.String](test/foo/kotlin.-string/index) | |
+
diff --git a/core/testdata/format/functionWithDefaultParameter.kt b/core/testdata/format/functionWithDefaultParameter.kt
new file mode 100644
index 00000000..3a3a102f
--- /dev/null
+++ b/core/testdata/format/functionWithDefaultParameter.kt
@@ -0,0 +1 @@
+fun f(x: String = "") {}
diff --git a/core/testdata/format/functionWithDefaultParameter.md b/core/testdata/format/functionWithDefaultParameter.md
new file mode 100644
index 00000000..21e9eff7
--- /dev/null
+++ b/core/testdata/format/functionWithDefaultParameter.md
@@ -0,0 +1,8 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`fun f(x:&nbsp;String&nbsp;=&nbsp;""): Unit`
+
+
diff --git a/core/testdata/format/htmlEscaping.html b/core/testdata/format/htmlEscaping.html
new file mode 100644
index 00000000..a485c08f
--- /dev/null
+++ b/core/testdata/format/htmlEscaping.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / x</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/x">x</a><br/>
+<br/>
+<h1>x</h1>
+<code><span class="keyword">fun </span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span> <span class="identifier">x</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">T</span><span class="symbol">?</span></code><br/>
+<p>Special characters: &lt; is "less than", &gt; is "greater than", &amp; is "ampersand"</p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/htmlEscaping.kt b/core/testdata/format/htmlEscaping.kt
new file mode 100644
index 00000000..8778d8a5
--- /dev/null
+++ b/core/testdata/format/htmlEscaping.kt
@@ -0,0 +1,4 @@
+/**
+ * Special characters: < is "less than", > is "greater than", & is "ampersand"
+ */
+fun x<T>(): T? = null
diff --git a/core/testdata/format/inheritedExtensions.kt b/core/testdata/format/inheritedExtensions.kt
new file mode 100644
index 00000000..e38fe00d
--- /dev/null
+++ b/core/testdata/format/inheritedExtensions.kt
@@ -0,0 +1,11 @@
+open class Foo
+
+class Bar : Foo()
+
+fun Foo.first() {
+
+}
+
+fun Bar.second() {
+
+}
diff --git a/core/testdata/format/inheritedExtensions.md b/core/testdata/format/inheritedExtensions.md
new file mode 100644
index 00000000..79137eac
--- /dev/null
+++ b/core/testdata/format/inheritedExtensions.md
@@ -0,0 +1,21 @@
+[test](test/index) / [Bar](test/-bar/index)
+
+
+# Bar
+
+`class Bar&nbsp;:&nbsp;[Foo](test/-foo/index)`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-bar/-init-) | `Bar()` |
+
+
+### Extension Functions
+
+
+| [first](test/first) | `fun [Foo](test/-foo/index).first(): Unit` |
+| [second](test/second) | `fun [Bar](test/-bar/index).second(): Unit` |
+
diff --git a/core/testdata/format/inheritedMembers.kt b/core/testdata/format/inheritedMembers.kt
new file mode 100644
index 00000000..2d0c4ca1
--- /dev/null
+++ b/core/testdata/format/inheritedMembers.kt
@@ -0,0 +1,12 @@
+open class Foo {
+ fun first() {
+ }
+
+ val firstProp: Int = 0
+}
+
+class Bar : Foo() {
+ fun second()
+
+ val secondProp: Int = 1
+}
diff --git a/core/testdata/format/inheritedMembers.md b/core/testdata/format/inheritedMembers.md
new file mode 100644
index 00000000..d58d3974
--- /dev/null
+++ b/core/testdata/format/inheritedMembers.md
@@ -0,0 +1,38 @@
+[test](test/index) / [Bar](test/-bar/index)
+
+
+# Bar
+
+`class Bar&nbsp;:&nbsp;[Foo](test/-foo/index)`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-bar/-init-) | `Bar()` |
+
+
+### Properties
+
+
+| [secondProp](test/-bar/second-prop) | `val secondProp: Int` |
+
+
+### Inherited Properties
+
+
+| [firstProp](test/-foo/first-prop) | `val firstProp: Int` |
+
+
+### Functions
+
+
+| [second](test/-bar/second) | `fun second(): Unit` |
+
+
+### Inherited Functions
+
+
+| [first](test/-foo/first) | `fun first(): Unit` |
+
diff --git a/core/testdata/format/javaCodeInParam.java b/core/testdata/format/javaCodeInParam.java
new file mode 100644
index 00000000..73025fcc
--- /dev/null
+++ b/core/testdata/format/javaCodeInParam.java
@@ -0,0 +1,5 @@
+/**
+ * @param T this is {@code some code} and other text
+ */
+class C<T> {
+}
diff --git a/core/testdata/format/javaCodeInParam.md b/core/testdata/format/javaCodeInParam.md
new file mode 100644
index 00000000..b1d2d871
--- /dev/null
+++ b/core/testdata/format/javaCodeInParam.md
@@ -0,0 +1,21 @@
+[test](test/index) / [C](test/-c/index)
+
+
+# C
+
+`protected open class C&lt;T&nbsp;:&nbsp;Any&gt;`
+
+
+
+
+### Parameters
+
+`T` - this is `some code` and other text
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-c/-init-) | `C()` |
+
diff --git a/core/testdata/format/javaCodeLiteralTags.java b/core/testdata/format/javaCodeLiteralTags.java
new file mode 100644
index 00000000..e71ddaa7
--- /dev/null
+++ b/core/testdata/format/javaCodeLiteralTags.java
@@ -0,0 +1,6 @@
+/**
+ * <p>{@code A<B>C}</p>
+ * <p>{@literal A<B>C}</p>
+ */
+class C {
+}
diff --git a/core/testdata/format/javaCodeLiteralTags.md b/core/testdata/format/javaCodeLiteralTags.md
new file mode 100644
index 00000000..83c9cc33
--- /dev/null
+++ b/core/testdata/format/javaCodeLiteralTags.md
@@ -0,0 +1,23 @@
+[test](test/index) / [C](test/-c/index)
+
+
+# C
+
+`protected open class C`
+
+
+`A&lt;B&gt;C`
+
+
+A&lt;B&gt;C
+
+
+
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-c/-init-) | `C()` |
+
diff --git a/core/testdata/format/javaDeprecated.html b/core/testdata/format/javaDeprecated.html
new file mode 100644
index 00000000..236c6490
--- /dev/null
+++ b/core/testdata/format/javaDeprecated.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / Foo.foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-foo/index">Foo</a>&nbsp;/&nbsp;<a href="test/-foo/foo">foo</a><br/>
+<br/>
+<h1>foo</h1>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><s><span class="identifier">foo</span></s><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<strong>Deprecated:</strong> use <code><a href="test/-foo/bar">#bar</a></code> instead <p></p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/javaDeprecated.java b/core/testdata/format/javaDeprecated.java
new file mode 100644
index 00000000..9694a444
--- /dev/null
+++ b/core/testdata/format/javaDeprecated.java
@@ -0,0 +1,5 @@
+class Foo {
+ /** @deprecated use {@link #bar} instead */
+ public void foo() {}
+ public void bar() {}
+}
diff --git a/core/testdata/format/javaLinkTag.html b/core/testdata/format/javaLinkTag.html
new file mode 100644
index 00000000..1b6921ab
--- /dev/null
+++ b/core/testdata/format/javaLinkTag.html
@@ -0,0 +1,36 @@
+<HTML>
+<HEAD>
+<title>test / Foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-foo/index">Foo</a><br/>
+<br/>
+<h1>Foo</h1>
+<code><span class="keyword">protected</span> <span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Foo</span></code><br/>
+<p>Call <code><a href="test/-foo/bar">#bar()</a></code> to do the job. </p>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Foo</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/bar">bar</a></td>
+<td>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">bar</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/javaLinkTag.java b/core/testdata/format/javaLinkTag.java
new file mode 100644
index 00000000..84f761c6
--- /dev/null
+++ b/core/testdata/format/javaLinkTag.java
@@ -0,0 +1,6 @@
+/**
+ * Call {@link #bar()} to do the job.
+ */
+class Foo {
+ public void bar()
+}
diff --git a/core/testdata/format/javaLinkTagWithLabel.html b/core/testdata/format/javaLinkTagWithLabel.html
new file mode 100644
index 00000000..aabfbc58
--- /dev/null
+++ b/core/testdata/format/javaLinkTagWithLabel.html
@@ -0,0 +1,36 @@
+<HTML>
+<HEAD>
+<title>test / Foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-foo/index">Foo</a><br/>
+<br/>
+<h1>Foo</h1>
+<code><span class="keyword">protected</span> <span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Foo</span></code><br/>
+<p>Call <code><a href="test/-foo/bar">this wonderful method</a></code> to do the job. </p>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Foo</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/bar">bar</a></td>
+<td>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">bar</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/javaLinkTagWithLabel.java b/core/testdata/format/javaLinkTagWithLabel.java
new file mode 100644
index 00000000..1db5ad70
--- /dev/null
+++ b/core/testdata/format/javaLinkTagWithLabel.java
@@ -0,0 +1,6 @@
+/**
+ * Call {@link #bar() this wonderful method} to do the job.
+ */
+class Foo {
+ public void bar()
+}
diff --git a/core/testdata/format/javaSeeTag.html b/core/testdata/format/javaSeeTag.html
new file mode 100644
index 00000000..a6813df3
--- /dev/null
+++ b/core/testdata/format/javaSeeTag.html
@@ -0,0 +1,38 @@
+<HTML>
+<HEAD>
+<title>test / Foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-foo/index">Foo</a><br/>
+<br/>
+<h1>Foo</h1>
+<code><span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Foo</span></code><br/>
+<p></p>
+<strong>See Also</strong><br/>
+<a href="test/-foo/bar">#bar</a><br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Foo</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-foo/bar">bar</a></td>
+<td>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">bar</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/javaSeeTag.java b/core/testdata/format/javaSeeTag.java
new file mode 100644
index 00000000..94a24606
--- /dev/null
+++ b/core/testdata/format/javaSeeTag.java
@@ -0,0 +1,6 @@
+/**
+ * @see #bar
+ */
+public class Foo {
+ public void bar() {}
+} \ No newline at end of file
diff --git a/core/testdata/format/javaSpaceInAuthor.java b/core/testdata/format/javaSpaceInAuthor.java
new file mode 100644
index 00000000..f980ae07
--- /dev/null
+++ b/core/testdata/format/javaSpaceInAuthor.java
@@ -0,0 +1,5 @@
+/**
+ * @author Dmitry Jemerov
+ */
+class C {
+} \ No newline at end of file
diff --git a/core/testdata/format/javaSpaceInAuthor.md b/core/testdata/format/javaSpaceInAuthor.md
new file mode 100644
index 00000000..4d19ed01
--- /dev/null
+++ b/core/testdata/format/javaSpaceInAuthor.md
@@ -0,0 +1,19 @@
+[test](test/index) / [C](test/-c/index)
+
+
+# C
+
+`protected open class C`
+
+
+
+**Author**
+Dmitry Jemerov
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-c/-init-) | `C()` |
+
diff --git a/core/testdata/format/javaSupertype.html b/core/testdata/format/javaSupertype.html
new file mode 100644
index 00000000..4c847281
--- /dev/null
+++ b/core/testdata/format/javaSupertype.html
@@ -0,0 +1,35 @@
+<HTML>
+<HEAD>
+<title>test / C.Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-c/index">C</a>&nbsp;/&nbsp;<a href="test/-c/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Bar</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="test/-c/-foo/index"><span class="identifier">Foo</span></a></code><br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-c/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-c/-bar/return-foo">returnFoo</a></td>
+<td>
+<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">returnFoo</span><span class="symbol">(</span><span class="identifier">foo</span><span class="symbol">:</span>&nbsp;<a href="test/-c/-foo/index"><span class="identifier">Foo</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="test/-c/-foo/index"><span class="identifier">Foo</span></a></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/javaSupertype.java b/core/testdata/format/javaSupertype.java
new file mode 100644
index 00000000..b3142ff6
--- /dev/null
+++ b/core/testdata/format/javaSupertype.java
@@ -0,0 +1,8 @@
+class C {
+ public static class Foo {
+ }
+
+ public static class Bar extends Foo {
+ public Foo returnFoo(Foo foo) { return foo; }
+ }
+}
diff --git a/core/testdata/format/javadocHtml.java b/core/testdata/format/javadocHtml.java
new file mode 100644
index 00000000..622116b2
--- /dev/null
+++ b/core/testdata/format/javadocHtml.java
@@ -0,0 +1,14 @@
+/**
+ * <b>Bold</b>
+ * <strong>Strong</strong>
+ * <i>Italic</i>
+ * <em>Emphasized</em>
+ * <p>Paragraph</p>
+ * <s>Strikethrough</s>
+ * <del>Deleted</del>
+ * <code>Code</code>
+ * <pre>Block code</pre>
+ * <ul><li>List Item</li></ul>
+ */
+public class C {
+}
diff --git a/core/testdata/format/javadocHtml.md b/core/testdata/format/javadocHtml.md
new file mode 100644
index 00000000..db71cc3b
--- /dev/null
+++ b/core/testdata/format/javadocHtml.md
@@ -0,0 +1,27 @@
+[test](test/index) / [C](test/-c/index)
+
+
+# C
+
+`open class C`
+
+**Bold** **Strong** *Italic* *Emphasized*
+Paragraph
+
+ ~~Strikethrough~~ ~~Deleted~~ `Code`
+```
+Block code
+```
+
+ * List Item
+
+
+
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-c/-init-) | `C()` |
+
diff --git a/core/testdata/format/javadocOrderedList.java b/core/testdata/format/javadocOrderedList.java
new file mode 100644
index 00000000..c32d9032
--- /dev/null
+++ b/core/testdata/format/javadocOrderedList.java
@@ -0,0 +1,8 @@
+/**
+ * <ol>
+ * <li>Rinse</li>
+ * <li>Repeat</li>
+ * </ol>
+ */
+public class Bar {
+}
diff --git a/core/testdata/format/javadocOrderedList.md b/core/testdata/format/javadocOrderedList.md
new file mode 100644
index 00000000..02a3c095
--- /dev/null
+++ b/core/testdata/format/javadocOrderedList.md
@@ -0,0 +1,20 @@
+[test](test/index) / [Bar](test/-bar/index)
+
+
+# Bar
+
+`open class Bar`
+
+ 1. Rinse
+ 1. Repeat
+
+
+
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-bar/-init-) | `Bar()` |
+
diff --git a/core/testdata/format/linkWithLabel.html b/core/testdata/format/linkWithLabel.html
new file mode 100644
index 00000000..75769ffd
--- /dev/null
+++ b/core/testdata/format/linkWithLabel.html
@@ -0,0 +1,37 @@
+<HTML>
+<HEAD>
+<title>test / Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">class </span><span class="identifier">Bar</span></code><br/>
+<p>Use <a href="test/-bar/foo">this method</a> for best results.</p>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code><p>Use <a href="test/-bar/foo">this method</a> for best results.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-bar/foo">foo</a></td>
+<td>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/linkWithLabel.kt b/core/testdata/format/linkWithLabel.kt
new file mode 100644
index 00000000..4a85c505
--- /dev/null
+++ b/core/testdata/format/linkWithLabel.kt
@@ -0,0 +1,6 @@
+/**
+ * Use [this method][Bar.foo] for best results.
+ */
+class Bar {
+ fun foo() {}
+}
diff --git a/core/testdata/format/nullability.kt b/core/testdata/format/nullability.kt
new file mode 100644
index 00000000..d1d4545b
--- /dev/null
+++ b/core/testdata/format/nullability.kt
@@ -0,0 +1,5 @@
+class C<T> {
+ fun foo(): Comparable<T>? {
+ return null
+ }
+}
diff --git a/core/testdata/format/nullability.md b/core/testdata/format/nullability.md
new file mode 100644
index 00000000..ee50a0a5
--- /dev/null
+++ b/core/testdata/format/nullability.md
@@ -0,0 +1,20 @@
+[test](test/index) / [C](test/-c/index)
+
+
+# C
+
+`class C&lt;T&gt;`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-c/-init-) | `C()` |
+
+
+### Functions
+
+
+| [foo](test/-c/foo) | `fun foo(): Comparable&lt;T&gt;?` |
+
diff --git a/core/testdata/format/operatorOverloading.kt b/core/testdata/format/operatorOverloading.kt
new file mode 100644
index 00000000..6fe78e45
--- /dev/null
+++ b/core/testdata/format/operatorOverloading.kt
@@ -0,0 +1,3 @@
+class C {
+ fun plus(other: C): C
+}
diff --git a/core/testdata/format/operatorOverloading.md b/core/testdata/format/operatorOverloading.md
new file mode 100644
index 00000000..05fe3206
--- /dev/null
+++ b/core/testdata/format/operatorOverloading.md
@@ -0,0 +1,8 @@
+[test](test/index) / [C](test/-c/index) / [plus](test/-c/plus)
+
+
+# plus
+
+`fun plus(other:&nbsp;[C](test/-c/index)): [C](test/-c/index)`
+
+
diff --git a/core/testdata/format/orderedList.html b/core/testdata/format/orderedList.html
new file mode 100644
index 00000000..9917568f
--- /dev/null
+++ b/core/testdata/format/orderedList.html
@@ -0,0 +1,30 @@
+<HTML>
+<HEAD>
+<title>test / Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">class </span><span class="identifier">Bar</span></code><br/>
+<p>Usage instructions:</p>
+<ol><li><p>Rinse</p>
+</li><li><p>Repeat</p>
+</li></ol><br/>
+<br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code><p>Usage instructions:</p>
+</td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/orderedList.kt b/core/testdata/format/orderedList.kt
new file mode 100644
index 00000000..03681c7a
--- /dev/null
+++ b/core/testdata/format/orderedList.kt
@@ -0,0 +1,8 @@
+/**
+ * Usage instructions:
+ *
+ * 1. Rinse
+ * 1. Repeat
+ */
+class Bar {
+}
diff --git a/core/testdata/format/overloads.html b/core/testdata/format/overloads.html
new file mode 100644
index 00000000..752f3989
--- /dev/null
+++ b/core/testdata/format/overloads.html
@@ -0,0 +1,23 @@
+<HTML>
+<HEAD>
+<title>test / root package</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a><br/>
+<br/>
+<h2>Package </h2>
+<h3>Functions</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/f">f</a></td>
+<td>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">Int</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">String</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>Performs an action on x.</p>
+</td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/overloads.kt b/core/testdata/format/overloads.kt
new file mode 100644
index 00000000..dcd2d097
--- /dev/null
+++ b/core/testdata/format/overloads.kt
@@ -0,0 +1,5 @@
+/** Performs an action on x. */
+fun f(x: Int) { }
+
+/** Performs an action on x. */
+fun f(x: String) { }
diff --git a/core/testdata/format/overloadsWithDescription.html b/core/testdata/format/overloadsWithDescription.html
new file mode 100644
index 00000000..a0efb472
--- /dev/null
+++ b/core/testdata/format/overloadsWithDescription.html
@@ -0,0 +1,21 @@
+<HTML>
+<HEAD>
+<title>test / f</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/f">f</a><br/>
+<br/>
+<h1>f</h1>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">Int</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">String</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>Performs an action on <a href="test/f#x">x</a>.</p>
+<p>This is a long description.</p>
+<br/>
+<br/>
+<h3>Parameters</h3>
+<a name="x"></a>
+<code>x</code> - the value to perform the action on.<br/>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/overloadsWithDescription.kt b/core/testdata/format/overloadsWithDescription.kt
new file mode 100644
index 00000000..740e642f
--- /dev/null
+++ b/core/testdata/format/overloadsWithDescription.kt
@@ -0,0 +1,15 @@
+/**
+ * Performs an action on [x].
+ *
+ * This is a long description.
+ * @param x the value to perform the action on.
+ */
+fun f(x: Int) { }
+
+/**
+ * Performs an action on [x].
+ *
+ * This is a long description.
+ * @param x the value to perform the action on.
+ */
+fun f(x: String) { }
diff --git a/core/testdata/format/overloadsWithDifferentDescriptions.html b/core/testdata/format/overloadsWithDifferentDescriptions.html
new file mode 100644
index 00000000..30a37e75
--- /dev/null
+++ b/core/testdata/format/overloadsWithDifferentDescriptions.html
@@ -0,0 +1,30 @@
+<HTML>
+<HEAD>
+<title>test / f</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/f">f</a><br/>
+<br/>
+<h1>f</h1>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">Int</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>Performs an action on x.</p>
+<p>This is a long description.</p>
+<br/>
+<br/>
+<h3>Parameters</h3>
+<a name="x"></a>
+<code>x</code> - the int value to perform the action on.<br/>
+<br/>
+<br/>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="identifier">x</span><span class="symbol">:</span>&nbsp;<span class="identifier">String</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>Performs an action on x.</p>
+<p>This is a long description.</p>
+<br/>
+<br/>
+<h3>Parameters</h3>
+<a name="x"></a>
+<code>x</code> - the string value to perform the action on.<br/>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/overloadsWithDifferentDescriptions.kt b/core/testdata/format/overloadsWithDifferentDescriptions.kt
new file mode 100644
index 00000000..ad3169b0
--- /dev/null
+++ b/core/testdata/format/overloadsWithDifferentDescriptions.kt
@@ -0,0 +1,15 @@
+/**
+ * Performs an action on x.
+ *
+ * This is a long description.
+ * @param x the int value to perform the action on.
+ */
+fun f(x: Int) { }
+
+/**
+ * Performs an action on x.
+ *
+ * This is a long description.
+ * @param x the string value to perform the action on.
+ */
+fun f(x: String) { }
diff --git a/core/testdata/format/overridingFunction.kt b/core/testdata/format/overridingFunction.kt
new file mode 100644
index 00000000..d7329489
--- /dev/null
+++ b/core/testdata/format/overridingFunction.kt
@@ -0,0 +1,7 @@
+open class C() {
+ open fun f() {}
+}
+
+class D(): C() {
+ override fun f() {}
+}
diff --git a/core/testdata/format/overridingFunction.md b/core/testdata/format/overridingFunction.md
new file mode 100644
index 00000000..2c898b9c
--- /dev/null
+++ b/core/testdata/format/overridingFunction.md
@@ -0,0 +1,9 @@
+[test](test/index) / [D](test/-d/index) / [f](test/-d/f)
+
+
+# f
+
+`fun f(): Unit`
+Overrides [C.f](test/-c/f)
+
+
diff --git a/core/testdata/format/paramTag.kt b/core/testdata/format/paramTag.kt
new file mode 100644
index 00000000..47e471f5
--- /dev/null
+++ b/core/testdata/format/paramTag.kt
@@ -0,0 +1,6 @@
+/**
+ * @param x A string
+ * @param y A number with a really long description that spans multiple lines and goes
+ * on and on and is very interesting to read
+ */
+fun f(x: String, y: Int) {}
diff --git a/core/testdata/format/paramTag.md b/core/testdata/format/paramTag.md
new file mode 100644
index 00000000..882083fe
--- /dev/null
+++ b/core/testdata/format/paramTag.md
@@ -0,0 +1,14 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`fun f(x:&nbsp;String, y:&nbsp;Int): Unit`
+
+### Parameters
+
+`x` - A string
+`y` - A number with a really long description that spans multiple lines and goes
+on and on and is very interesting to read
+
+
diff --git a/core/testdata/format/parameterAnchor.html b/core/testdata/format/parameterAnchor.html
new file mode 100644
index 00000000..c5920fb7
--- /dev/null
+++ b/core/testdata/format/parameterAnchor.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<title>test / processFiles</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/process-files">processFiles</a><br/>
+<br/>
+<h1>processFiles</h1>
+<code><span class="keyword">fun </span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span> <span class="identifier">processFiles</span><span class="symbol">(</span><span class="identifier">processor</span><span class="symbol">:</span>&nbsp;<span class="symbol">(</span><span class="symbol">)</span>&nbsp;<span class="symbol">-&gt;</span>&nbsp;<span class="identifier">T</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><span class="identifier">T</span><span class="symbol">&gt;</span></code><br/>
+<p>Runs <a href="test/process-files#processor">processor</a> for each file and collects its results into single list</p>
+<h3>Parameters</h3>
+<a name="processor"></a>
+<code>processor</code> - function to receive context for symbol resolution and file for processing<br/>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/parameterAnchor.kt b/core/testdata/format/parameterAnchor.kt
new file mode 100644
index 00000000..ae36ee4c
--- /dev/null
+++ b/core/testdata/format/parameterAnchor.kt
@@ -0,0 +1,6 @@
+/**
+ * Runs [processor] for each file and collects its results into single list
+ * @param processor function to receive context for symbol resolution and file for processing
+ */
+public fun processFiles<T>(processor: () -> T): List<T> {
+}
diff --git a/core/testdata/format/parenthesis.html b/core/testdata/format/parenthesis.html
new file mode 100644
index 00000000..c36b311b
--- /dev/null
+++ b/core/testdata/format/parenthesis.html
@@ -0,0 +1,14 @@
+<HTML>
+<HEAD>
+<title>test / foo</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/foo">foo</a><br/>
+<br/>
+<h1>foo</h1>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>foo (bar)</p>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/parenthesis.kt b/core/testdata/format/parenthesis.kt
new file mode 100644
index 00000000..b906f64a
--- /dev/null
+++ b/core/testdata/format/parenthesis.kt
@@ -0,0 +1,4 @@
+/**
+ * foo (bar)
+ */
+fun foo() {}
diff --git a/core/testdata/format/propertyVar.kt b/core/testdata/format/propertyVar.kt
new file mode 100644
index 00000000..88be1a7a
--- /dev/null
+++ b/core/testdata/format/propertyVar.kt
@@ -0,0 +1 @@
+var x = 1 \ No newline at end of file
diff --git a/core/testdata/format/propertyVar.md b/core/testdata/format/propertyVar.md
new file mode 100644
index 00000000..f2110992
--- /dev/null
+++ b/core/testdata/format/propertyVar.md
@@ -0,0 +1,8 @@
+[test](test/index) / [x](test/x)
+
+
+# x
+
+`var x: Int`
+
+
diff --git a/core/testdata/format/reifiedTypeParameter.kt b/core/testdata/format/reifiedTypeParameter.kt
new file mode 100644
index 00000000..00fa1dc9
--- /dev/null
+++ b/core/testdata/format/reifiedTypeParameter.kt
@@ -0,0 +1,3 @@
+inline fun f<reified T>() {
+
+}
diff --git a/core/testdata/format/reifiedTypeParameter.md b/core/testdata/format/reifiedTypeParameter.md
new file mode 100644
index 00000000..2e96018e
--- /dev/null
+++ b/core/testdata/format/reifiedTypeParameter.md
@@ -0,0 +1,8 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`inline fun &lt;reified&nbsp;T&gt; f(): Unit`
+
+
diff --git a/core/testdata/format/see.html b/core/testdata/format/see.html
new file mode 100644
index 00000000..b3ffb74b
--- /dev/null
+++ b/core/testdata/format/see.html
@@ -0,0 +1,28 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/quux">quux</a><br/>
+<br/>
+<h1>quux</h1>
+<code><span class="keyword">fun </span><span class="identifier">quux</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<strong>See Also</strong><br/>
+<p><a href="test/foo">foo</a></p>
+<p><a href="test/bar">bar</a></p>
+<br/>
+<br/>
+<br/>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/foo">foo</a><br/>
+<br/>
+<h1>foo</h1>
+<code><span class="keyword">fun </span><span class="identifier">foo</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<br/>
+<br/>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/bar">bar</a><br/>
+<br/>
+<h1>bar</h1>
+<code><span class="keyword">fun </span><span class="identifier">bar</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/see.kt b/core/testdata/format/see.kt
new file mode 100644
index 00000000..a0b153b0
--- /dev/null
+++ b/core/testdata/format/see.kt
@@ -0,0 +1,12 @@
+/**
+ * @see foo
+ * @see bar
+ */
+fun quux() {
+}
+
+fun foo() {
+}
+
+fun bar() {
+} \ No newline at end of file
diff --git a/core/testdata/format/starProjection.kt b/core/testdata/format/starProjection.kt
new file mode 100644
index 00000000..48d53e47
--- /dev/null
+++ b/core/testdata/format/starProjection.kt
@@ -0,0 +1,3 @@
+public fun Iterable<*>.containsFoo(element: Any?): Boolean {
+ return false
+}
diff --git a/core/testdata/format/starProjection.md b/core/testdata/format/starProjection.md
new file mode 100644
index 00000000..c9be2f58
--- /dev/null
+++ b/core/testdata/format/starProjection.md
@@ -0,0 +1,8 @@
+[test](test/index) / [kotlin.Iterable](test/kotlin.-iterable/index)
+
+
+### Extensions for kotlin.Iterable
+
+
+| [containsFoo](test/kotlin.-iterable/contains-foo) | `fun Iterable&lt;*&gt;.containsFoo(element:&nbsp;Any?): Boolean` |
+
diff --git a/core/testdata/format/summarizeSignatures.kt b/core/testdata/format/summarizeSignatures.kt
new file mode 100644
index 00000000..1d875a50
--- /dev/null
+++ b/core/testdata/format/summarizeSignatures.kt
@@ -0,0 +1,20 @@
+package kotlin
+
+class Array<T>
+class IntArray
+class CharArray
+
+/**
+ * Returns true if foo.
+ */
+fun IntArray.foo(predicate: (Int) -> Boolean): Boolean = false
+
+/**
+ * Returns true if foo.
+ */
+fun CharArray.foo(predicate: (Char) -> Boolean): Boolean = false
+
+/**
+ * Returns true if foo.
+ */
+fun <T> Array<T>.foo(predicate: (T) -> Boolean): Boolean = false
diff --git a/core/testdata/format/summarizeSignatures.md b/core/testdata/format/summarizeSignatures.md
new file mode 100644
index 00000000..b1707f40
--- /dev/null
+++ b/core/testdata/format/summarizeSignatures.md
@@ -0,0 +1,23 @@
+[test](test/index) / [kotlin](test/kotlin/index)
+
+
+## Package kotlin
+
+
+### Types
+
+
+| [Array](test/kotlin/-array/index) | `class Array&lt;T&gt;` |
+| [CharArray](test/kotlin/-char-array/index) | `class CharArray` |
+| [IntArray](test/kotlin/-int-array/index) | `class IntArray` |
+
+
+### Functions
+
+
+| [foo](test/kotlin/foo) | `fun &lt;T&gt; any_array&lt;T&gt;.foo(predicate:&nbsp;(T)&nbsp;-&gt;&nbsp;Boolean): Boolean`
+
+Returns true if foo.
+
+ |
+
diff --git a/core/testdata/format/summarizeSignaturesProperty.kt b/core/testdata/format/summarizeSignaturesProperty.kt
new file mode 100644
index 00000000..fbbdd328
--- /dev/null
+++ b/core/testdata/format/summarizeSignaturesProperty.kt
@@ -0,0 +1,20 @@
+package kotlin
+
+class Array<T>
+class IntArray
+class CharArray
+
+/**
+ * Returns true if foo.
+ */
+val IntArray.foo: Int = 0
+
+/**
+ * Returns true if foo.
+ */
+val CharArray.foo: Int = 0
+
+/**
+ * Returns true if foo.
+ */
+val <T> Array<T>.foo: Int = 0
diff --git a/core/testdata/format/summarizeSignaturesProperty.md b/core/testdata/format/summarizeSignaturesProperty.md
new file mode 100644
index 00000000..9646b0f1
--- /dev/null
+++ b/core/testdata/format/summarizeSignaturesProperty.md
@@ -0,0 +1,23 @@
+[test](test/index) / [kotlin](test/kotlin/index)
+
+
+## Package kotlin
+
+
+### Types
+
+
+| [Array](test/kotlin/-array/index) | `class Array&lt;T&gt;` |
+| [CharArray](test/kotlin/-char-array/index) | `class CharArray` |
+| [IntArray](test/kotlin/-int-array/index) | `class IntArray` |
+
+
+### Properties
+
+
+| [foo](test/kotlin/foo) | `val &lt;T&gt; any_array&lt;T&gt;.foo: Int`
+
+Returns true if foo.
+
+ |
+
diff --git a/core/testdata/format/throwsTag.kt b/core/testdata/format/throwsTag.kt
new file mode 100644
index 00000000..29a9c3f7
--- /dev/null
+++ b/core/testdata/format/throwsTag.kt
@@ -0,0 +1,5 @@
+/**
+ * @throws IllegalArgumentException on Mondays
+ * @exception NullPointerException on Tuesdays
+ */
+fun f() {}
diff --git a/core/testdata/format/throwsTag.md b/core/testdata/format/throwsTag.md
new file mode 100644
index 00000000..d0a4e610
--- /dev/null
+++ b/core/testdata/format/throwsTag.md
@@ -0,0 +1,13 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`fun f(): Unit`
+
+### Exceptions
+
+`IllegalArgumentException` - on Mondays
+`NullPointerException` - on Tuesdays
+
+
diff --git a/core/testdata/format/tripleBackticks.html b/core/testdata/format/tripleBackticks.html
new file mode 100644
index 00000000..13954985
--- /dev/null
+++ b/core/testdata/format/tripleBackticks.html
@@ -0,0 +1,16 @@
+<HTML>
+<HEAD>
+<title>test / f</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/f">f</a><br/>
+<br/>
+<h1>f</h1>
+<code><span class="keyword">fun </span><span class="identifier">f</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
+<p>Description</p>
+<pre><code>code sample</code></pre><br/>
+<br/>
+<br/>
+<br/>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/tripleBackticks.kt b/core/testdata/format/tripleBackticks.kt
new file mode 100644
index 00000000..54dfa6d5
--- /dev/null
+++ b/core/testdata/format/tripleBackticks.kt
@@ -0,0 +1,7 @@
+/**
+ * Description
+ * ```
+ * code sample
+ * ```
+ */
+fun f() {}
diff --git a/core/testdata/format/typeLink.html b/core/testdata/format/typeLink.html
new file mode 100644
index 00000000..ff99090a
--- /dev/null
+++ b/core/testdata/format/typeLink.html
@@ -0,0 +1,24 @@
+<HTML>
+<HEAD>
+<title>test / Bar</title>
+</HEAD>
+<BODY>
+<a href="test/index">test</a>&nbsp;/&nbsp;<a href="test/-bar/index">Bar</a><br/>
+<br/>
+<h1>Bar</h1>
+<code><span class="keyword">class </span><span class="identifier">Bar</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="test/-foo/index"><span class="identifier">Foo</span></a></code><br/>
+<br/>
+<br/>
+<h3>Constructors</h3>
+<table>
+<tbody>
+<tr>
+<td>
+<a href="test/-bar/-init-">&lt;init&gt;</a></td>
+<td>
+<code><span class="identifier">Bar</span><span class="symbol">(</span><span class="symbol">)</span></code></td>
+</tr>
+</tbody>
+</table>
+</BODY>
+</HTML>
diff --git a/core/testdata/format/typeLink.kt b/core/testdata/format/typeLink.kt
new file mode 100644
index 00000000..966e020e
--- /dev/null
+++ b/core/testdata/format/typeLink.kt
@@ -0,0 +1,5 @@
+class Foo() {
+}
+
+class Bar(): Foo {
+}
diff --git a/core/testdata/format/typeParameterBounds.kt b/core/testdata/format/typeParameterBounds.kt
new file mode 100644
index 00000000..5f22f8c5
--- /dev/null
+++ b/core/testdata/format/typeParameterBounds.kt
@@ -0,0 +1,6 @@
+
+/**
+ * generic function
+ */
+public fun <T : R, R> generic() {
+} \ No newline at end of file
diff --git a/core/testdata/format/typeParameterBounds.md b/core/testdata/format/typeParameterBounds.md
new file mode 100644
index 00000000..fe597878
--- /dev/null
+++ b/core/testdata/format/typeParameterBounds.md
@@ -0,0 +1,11 @@
+[test](test/index) / [generic](test/generic)
+
+
+# generic
+
+`fun &lt;T&nbsp;:&nbsp;R, R&gt; generic(): Unit`
+
+generic function
+
+
+
diff --git a/core/testdata/format/typeParameterVariance.kt b/core/testdata/format/typeParameterVariance.kt
new file mode 100644
index 00000000..28b29f4b
--- /dev/null
+++ b/core/testdata/format/typeParameterVariance.kt
@@ -0,0 +1,2 @@
+class Foo<out T> {
+}
diff --git a/core/testdata/format/typeParameterVariance.md b/core/testdata/format/typeParameterVariance.md
new file mode 100644
index 00000000..c0bfa6de
--- /dev/null
+++ b/core/testdata/format/typeParameterVariance.md
@@ -0,0 +1,14 @@
+[test](test/index) / [Foo](test/-foo/index)
+
+
+# Foo
+
+`class Foo&lt;out&nbsp;T&gt;`
+
+
+
+### Constructors
+
+
+| [&lt;init&gt;](test/-foo/-init-) | `Foo()` |
+
diff --git a/core/testdata/format/typeProjectionVariance.kt b/core/testdata/format/typeProjectionVariance.kt
new file mode 100644
index 00000000..85ee344d
--- /dev/null
+++ b/core/testdata/format/typeProjectionVariance.kt
@@ -0,0 +1 @@
+fun <T> Array<out T>.foo() {}
diff --git a/core/testdata/format/typeProjectionVariance.md b/core/testdata/format/typeProjectionVariance.md
new file mode 100644
index 00000000..7aa34593
--- /dev/null
+++ b/core/testdata/format/typeProjectionVariance.md
@@ -0,0 +1,8 @@
+[test](test/index) / [kotlin.Array](test/kotlin.-array/index)
+
+
+### Extensions for kotlin.Array
+
+
+| [foo](test/kotlin.-array/foo) | `fun &lt;T&gt; Array&lt;out&nbsp;T&gt;.foo(): Unit` |
+
diff --git a/core/testdata/format/varargsFunction.kt b/core/testdata/format/varargsFunction.kt
new file mode 100644
index 00000000..deea127b
--- /dev/null
+++ b/core/testdata/format/varargsFunction.kt
@@ -0,0 +1 @@
+fun f(vararg s: String) {}
diff --git a/core/testdata/format/varargsFunction.md b/core/testdata/format/varargsFunction.md
new file mode 100644
index 00000000..723437aa
--- /dev/null
+++ b/core/testdata/format/varargsFunction.md
@@ -0,0 +1,8 @@
+[test](test/index) / [f](test/f)
+
+
+# f
+
+`fun f(vararg s:&nbsp;String): Unit`
+
+
diff --git a/core/testdata/functions/annotatedFunction.kt b/core/testdata/functions/annotatedFunction.kt
new file mode 100644
index 00000000..f7abbf6c
--- /dev/null
+++ b/core/testdata/functions/annotatedFunction.kt
@@ -0,0 +1,2 @@
+@Strictfp fun f() {
+}
diff --git a/core/testdata/functions/annotatedFunctionWithAnnotationParameters.kt b/core/testdata/functions/annotatedFunctionWithAnnotationParameters.kt
new file mode 100644
index 00000000..e559713a
--- /dev/null
+++ b/core/testdata/functions/annotatedFunctionWithAnnotationParameters.kt
@@ -0,0 +1,7 @@
+@Target(AnnotationTarget.VALUE_PARAMETER)
+@Retention(AnnotationRetention.SOURCE)
+@MustBeDocumented
+public annotation class Fancy(val size: Int)
+
+
+@Fancy(1) fun f() {}
diff --git a/core/testdata/functions/function.kt b/core/testdata/functions/function.kt
new file mode 100644
index 00000000..3ed81dfa
--- /dev/null
+++ b/core/testdata/functions/function.kt
@@ -0,0 +1,5 @@
+/**
+ * Function fn
+ */
+fun fn() {
+} \ No newline at end of file
diff --git a/core/testdata/functions/functionWithAnnotatedParam.kt b/core/testdata/functions/functionWithAnnotatedParam.kt
new file mode 100644
index 00000000..f858e671
--- /dev/null
+++ b/core/testdata/functions/functionWithAnnotatedParam.kt
@@ -0,0 +1,7 @@
+@Target(AnnotationTarget.VALUE_PARAMETER)
+@Retention(AnnotationRetention.SOURCE)
+@MustBeDocumented
+public annotation class Fancy
+
+fun function(@Fancy notInlined: () -> Unit) {
+}
diff --git a/core/testdata/functions/functionWithDefaultParameter.kt b/core/testdata/functions/functionWithDefaultParameter.kt
new file mode 100644
index 00000000..3a3a102f
--- /dev/null
+++ b/core/testdata/functions/functionWithDefaultParameter.kt
@@ -0,0 +1 @@
+fun f(x: String = "") {}
diff --git a/core/testdata/functions/functionWithNoinlineParam.kt b/core/testdata/functions/functionWithNoinlineParam.kt
new file mode 100644
index 00000000..640bec83
--- /dev/null
+++ b/core/testdata/functions/functionWithNoinlineParam.kt
@@ -0,0 +1,2 @@
+fun function(noinline notInlined: () -> Unit) {
+}
diff --git a/core/testdata/functions/functionWithNotDocumentedAnnotation.kt b/core/testdata/functions/functionWithNotDocumentedAnnotation.kt
new file mode 100644
index 00000000..3c7e2ff9
--- /dev/null
+++ b/core/testdata/functions/functionWithNotDocumentedAnnotation.kt
@@ -0,0 +1,2 @@
+@Suppress("FOO") fun f() {
+}
diff --git a/core/testdata/functions/functionWithParams.kt b/core/testdata/functions/functionWithParams.kt
new file mode 100644
index 00000000..85c49368
--- /dev/null
+++ b/core/testdata/functions/functionWithParams.kt
@@ -0,0 +1,8 @@
+/**
+ * Multiline
+ *
+ * Function
+ * Documentation
+ */
+fun function(/** parameter */ x: Int) {
+} \ No newline at end of file
diff --git a/core/testdata/functions/functionWithReceiver.kt b/core/testdata/functions/functionWithReceiver.kt
new file mode 100644
index 00000000..c8473251
--- /dev/null
+++ b/core/testdata/functions/functionWithReceiver.kt
@@ -0,0 +1,11 @@
+/**
+ * Function with receiver
+ */
+fun String.fn() {
+}
+
+/**
+ * Function with receiver
+ */
+fun String.fn(x: Int) {
+}
diff --git a/core/testdata/functions/genericFunction.kt b/core/testdata/functions/genericFunction.kt
new file mode 100644
index 00000000..05a65070
--- /dev/null
+++ b/core/testdata/functions/genericFunction.kt
@@ -0,0 +1,5 @@
+
+/**
+ * generic function
+ */
+private fun <T> generic() {} \ No newline at end of file
diff --git a/core/testdata/functions/genericFunctionWithConstraints.kt b/core/testdata/functions/genericFunctionWithConstraints.kt
new file mode 100644
index 00000000..5f22f8c5
--- /dev/null
+++ b/core/testdata/functions/genericFunctionWithConstraints.kt
@@ -0,0 +1,6 @@
+
+/**
+ * generic function
+ */
+public fun <T : R, R> generic() {
+} \ No newline at end of file
diff --git a/core/testdata/functions/inlineFunction.kt b/core/testdata/functions/inlineFunction.kt
new file mode 100644
index 00000000..11c19672
--- /dev/null
+++ b/core/testdata/functions/inlineFunction.kt
@@ -0,0 +1,2 @@
+inline fun f() {
+}
diff --git a/core/testdata/java/annotatedAnnotation.java b/core/testdata/java/annotatedAnnotation.java
new file mode 100644
index 00000000..6296e400
--- /dev/null
+++ b/core/testdata/java/annotatedAnnotation.java
@@ -0,0 +1,6 @@
+import java.lang.annotation.*;
+
+@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
+public @interface Attribute {
+ String value() default "";
+}
diff --git a/core/testdata/java/arrayType.java b/core/testdata/java/arrayType.java
new file mode 100644
index 00000000..dc42a207
--- /dev/null
+++ b/core/testdata/java/arrayType.java
@@ -0,0 +1,5 @@
+class Test {
+ public String[] arrayToString(int[] data) {
+ return null;
+ }
+}
diff --git a/core/testdata/java/constructors.java b/core/testdata/java/constructors.java
new file mode 100644
index 00000000..6f899d18
--- /dev/null
+++ b/core/testdata/java/constructors.java
@@ -0,0 +1,5 @@
+class Test {
+ public Test() {}
+
+ public Test(String s) {}
+}
diff --git a/core/testdata/java/deprecation.java b/core/testdata/java/deprecation.java
new file mode 100644
index 00000000..be8decd6
--- /dev/null
+++ b/core/testdata/java/deprecation.java
@@ -0,0 +1,6 @@
+class C {
+ /**
+ * @deprecated This should no longer be used
+ */
+ void fn() {}
+} \ No newline at end of file
diff --git a/core/testdata/java/enumValues.java b/core/testdata/java/enumValues.java
new file mode 100644
index 00000000..d8dda934
--- /dev/null
+++ b/core/testdata/java/enumValues.java
@@ -0,0 +1,3 @@
+enum E {
+ Foo
+} \ No newline at end of file
diff --git a/core/testdata/java/field.java b/core/testdata/java/field.java
new file mode 100644
index 00000000..d9ae75f9
--- /dev/null
+++ b/core/testdata/java/field.java
@@ -0,0 +1,4 @@
+class Test {
+ public int i;
+ public static final String s;
+}
diff --git a/core/testdata/java/inheritorLinks.java b/core/testdata/java/inheritorLinks.java
new file mode 100644
index 00000000..2378f5c6
--- /dev/null
+++ b/core/testdata/java/inheritorLinks.java
@@ -0,0 +1,7 @@
+class C {
+ public static class Foo {
+ }
+
+ public static class Bar extends Foo {
+ }
+}
diff --git a/core/testdata/java/innerClass.java b/core/testdata/java/innerClass.java
new file mode 100644
index 00000000..0f1be948
--- /dev/null
+++ b/core/testdata/java/innerClass.java
@@ -0,0 +1,4 @@
+class Test {
+ public class D {
+ }
+}
diff --git a/core/testdata/java/javaLangObject.java b/core/testdata/java/javaLangObject.java
new file mode 100644
index 00000000..be3dd570
--- /dev/null
+++ b/core/testdata/java/javaLangObject.java
@@ -0,0 +1,3 @@
+class Test {
+ public Object fn() { return null; }
+}
diff --git a/core/testdata/java/member.java b/core/testdata/java/member.java
new file mode 100644
index 00000000..d4f4b8d5
--- /dev/null
+++ b/core/testdata/java/member.java
@@ -0,0 +1,11 @@
+class Test {
+ /**
+ * Summary for Function
+ * @param name is String parameter
+ * @param value is int parameter
+ * @author yole
+ */
+ public void fn(String name, int value) {
+
+ }
+} \ No newline at end of file
diff --git a/core/testdata/java/memberWithModifiers.java b/core/testdata/java/memberWithModifiers.java
new file mode 100644
index 00000000..ea645c21
--- /dev/null
+++ b/core/testdata/java/memberWithModifiers.java
@@ -0,0 +1,12 @@
+public abstract class Test {
+ /**
+ * Summary for Function
+ * @param name is String parameter
+ * @param value is int parameter
+ */
+ protected final void fn(String name, int value) {
+
+ }
+
+ protected void openFn() {}
+} \ No newline at end of file
diff --git a/core/testdata/java/staticMethod.java b/core/testdata/java/staticMethod.java
new file mode 100644
index 00000000..a2ecd7f1
--- /dev/null
+++ b/core/testdata/java/staticMethod.java
@@ -0,0 +1,4 @@
+class C {
+ public static void foo() {
+ }
+}
diff --git a/core/testdata/java/superClass.java b/core/testdata/java/superClass.java
new file mode 100644
index 00000000..31b5fa96
--- /dev/null
+++ b/core/testdata/java/superClass.java
@@ -0,0 +1,2 @@
+public class Foo extends Exception implements Cloneable {
+}
diff --git a/core/testdata/java/typeParameter.java b/core/testdata/java/typeParameter.java
new file mode 100644
index 00000000..5a24b30a
--- /dev/null
+++ b/core/testdata/java/typeParameter.java
@@ -0,0 +1,3 @@
+class Foo<T extends Comparable<T>> {
+ public <E> E foo();
+}
diff --git a/core/testdata/java/varargs.java b/core/testdata/java/varargs.java
new file mode 100644
index 00000000..d184564e
--- /dev/null
+++ b/core/testdata/java/varargs.java
@@ -0,0 +1,3 @@
+class Foo {
+ public void bar(String... x);
+}
diff --git a/core/testdata/javadoc/obj.kt b/core/testdata/javadoc/obj.kt
new file mode 100644
index 00000000..1d10a422
--- /dev/null
+++ b/core/testdata/javadoc/obj.kt
@@ -0,0 +1,7 @@
+package foo
+
+class O {
+ companion object {
+
+ }
+}
diff --git a/core/testdata/javadoc/types.kt b/core/testdata/javadoc/types.kt
new file mode 100644
index 00000000..55be6058
--- /dev/null
+++ b/core/testdata/javadoc/types.kt
@@ -0,0 +1,4 @@
+package foo
+
+fun foo(x: Int, o: Any): String {
+}
diff --git a/core/testdata/links/linkToJDK.kt b/core/testdata/links/linkToJDK.kt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/core/testdata/links/linkToJDK.kt
diff --git a/core/testdata/links/linkToMember.kt b/core/testdata/links/linkToMember.kt
new file mode 100644
index 00000000..b60eaedb
--- /dev/null
+++ b/core/testdata/links/linkToMember.kt
@@ -0,0 +1,6 @@
+/**
+ * This is link to [member]
+ */
+class Foo {
+ fun member() {}
+} \ No newline at end of file
diff --git a/core/testdata/links/linkToParam.kt b/core/testdata/links/linkToParam.kt
new file mode 100644
index 00000000..ca42a742
--- /dev/null
+++ b/core/testdata/links/linkToParam.kt
@@ -0,0 +1,5 @@
+/**
+ * This is link to [param]
+ */
+fun Foo(param: String) {
+} \ No newline at end of file
diff --git a/core/testdata/links/linkToQualifiedMember.kt b/core/testdata/links/linkToQualifiedMember.kt
new file mode 100644
index 00000000..22c154fe
--- /dev/null
+++ b/core/testdata/links/linkToQualifiedMember.kt
@@ -0,0 +1,6 @@
+/**
+ * This is link to [Foo.member]
+ */
+class Foo {
+ fun member() {}
+} \ No newline at end of file
diff --git a/core/testdata/links/linkToSelf.kt b/core/testdata/links/linkToSelf.kt
new file mode 100644
index 00000000..74395f0f
--- /dev/null
+++ b/core/testdata/links/linkToSelf.kt
@@ -0,0 +1,6 @@
+/**
+ * This is link to [Foo]
+ */
+class Foo {
+
+} \ No newline at end of file
diff --git a/core/testdata/markdown/spec.txt b/core/testdata/markdown/spec.txt
new file mode 100644
index 00000000..fce87924
--- /dev/null
+++ b/core/testdata/markdown/spec.txt
@@ -0,0 +1,6150 @@
+---
+title: CommonMark Spec
+author:
+- John MacFarlane
+version: 2
+date: 2014-09-19
+...
+
+# Introduction
+
+## What is Markdown?
+
+Markdown is a plain text format for writing structured documents,
+based on conventions used for indicating formatting in email and
+usenet posts. It was developed in 2004 by John Gruber, who wrote
+the first Markdown-to-HTML converter in perl, and it soon became
+widely used in websites. By 2014 there were dozens of
+implementations in many languages. Some of them extended basic
+Markdown syntax with conventions for footnotes, definition lists,
+tables, and other constructs, and some allowed output not just in
+HTML but in LaTeX and many other formats.
+
+## Why is a spec needed?
+
+John Gruber's [canonical description of Markdown's
+syntax](http://daringfireball.net/projects/markdown/syntax)
+does not specify the syntax unambiguously. Here are some examples of
+questions it does not answer:
+
+1. How much indentation is needed for a sublist? The spec says that
+ continuation paragraphs need to be indented four spaces, but is
+ not fully explicit about sublists. It is natural to think that
+ they, too, must be indented four spaces, but `Markdown.pl` does
+ not require that. This is hardly a "corner case," and divergences
+ between implementations on this issue often lead to surprises for
+ users in real documents. (See [this comment by John
+ Gruber](http://article.gmane.org/gmane.text.markdown.general/1997).)
+
+2. Is a blank line needed before a block quote or header?
+ Most implementations do not require the blank line. However,
+ this can lead to unexpected results in hard-wrapped text, and
+ also to ambiguities in parsing (note that some implementations
+ put the header inside the blockquote, while others do not).
+ (John Gruber has also spoken [in favor of requiring the blank
+ lines](http://article.gmane.org/gmane.text.markdown.general/2146).)
+
+3. Is a blank line needed before an indented code block?
+ (`Markdown.pl` requires it, but this is not mentioned in the
+ documentation, and some implementations do not require it.)
+
+ ``` markdown
+ paragraph
+ code?
+ ```
+
+4. What is the exact rule for determining when list items get
+ wrapped in `<p>` tags? Can a list be partially "loose" and partially
+ "tight"? What should we do with a list like this?
+
+ ``` markdown
+ 1. one
+
+ 2. two
+ 3. three
+ ```
+
+ Or this?
+
+ ``` markdown
+ 1. one
+ - a
+
+ - b
+ 2. two
+ ```
+
+ (There are some relevant comments by John Gruber
+ [here](http://article.gmane.org/gmane.text.markdown.general/2554).)
+
+5. Can list markers be indented? Can ordered list markers be right-aligned?
+
+ ``` markdown
+ 8. item 1
+ 9. item 2
+ 10. item 2a
+ ```
+
+6. Is this one list with a horizontal rule in its second item,
+ or two lists separated by a horizontal rule?
+
+ ``` markdown
+ * a
+ * * * * *
+ * b
+ ```
+
+7. When list markers change from numbers to bullets, do we have
+ two lists or one? (The Markdown syntax description suggests two,
+ but the perl scripts and many other implementations produce one.)
+
+ ``` markdown
+ 1. fee
+ 2. fie
+ - foe
+ - fum
+ ```
+
+8. What are the precedence rules for the markers of inline structure?
+ For example, is the following a valid link, or does the code span
+ take precedence ?
+
+ ``` markdown
+ [a backtick (`)](/url) and [another backtick (`)](/url).
+ ```
+
+9. What are the precedence rules for markers of emphasis and strong
+ emphasis? For example, how should the following be parsed?
+
+ ``` markdown
+ *foo *bar* baz*
+ ```
+
+10. What are the precedence rules between block-level and inline-level
+ structure? For example, how should the following be parsed?
+
+ ``` markdown
+ - `a long code span can contain a hyphen like this
+ - and it can screw things up`
+ ```
+
+11. Can list items include headers? (`Markdown.pl` does not allow this,
+ but headers can occur in blockquotes.)
+
+ ``` markdown
+ - # Heading
+ ```
+
+12. Can link references be defined inside block quotes or list items?
+
+ ``` markdown
+ > Blockquote [foo].
+ >
+ > [foo]: /url
+ ```
+
+13. If there are multiple definitions for the same reference, which takes
+ precedence?
+
+ ``` markdown
+ [foo]: /url1
+ [foo]: /url2
+
+ [foo][]
+ ```
+
+In the absence of a spec, early implementers consulted `Markdown.pl`
+to resolve these ambiguities. But `Markdown.pl` was quite buggy, and
+gave manifestly bad results in many cases, so it was not a
+satisfactory replacement for a spec.
+
+Because there is no unambiguous spec, implementations have diverged
+considerably. As a result, users are often surprised to find that
+a document that renders one way on one system (say, a github wiki)
+renders differently on another (say, converting to docbook using
+pandoc). To make matters worse, because nothing in Markdown counts
+as a "syntax error," the divergence often isn't discovered right away.
+
+## About this document
+
+This document attempts to specify Markdown syntax unambiguously.
+It contains many examples with side-by-side Markdown and
+HTML. These are intended to double as conformance tests. An
+accompanying script `runtests.pl` can be used to run the tests
+against any Markdown program:
+
+ perl runtests.pl spec.txt PROGRAM
+
+Since this document describes how Markdown is to be parsed into
+an abstract syntax tree, it would have made sense to use an abstract
+representation of the syntax tree instead of HTML. But HTML is capable
+of representing the structural distinctions we need to make, and the
+choice of HTML for the tests makes it possible to run the tests against
+an implementation without writing an abstract syntax tree renderer.
+
+This document is generated from a text file, `spec.txt`, written
+in Markdown with a small extension for the side-by-side tests.
+The script `spec2md.pl` can be used to turn `spec.txt` into pandoc
+Markdown, which can then be converted into other formats.
+
+In the examples, the `→` character is used to represent tabs.
+
+# Preprocessing
+
+A [line](#line) <a id="line"></a>
+is a sequence of zero or more characters followed by a line
+ending (CR, LF, or CRLF) or by the end of
+file.
+
+This spec does not specify an encoding; it thinks of lines as composed
+of characters rather than bytes. A conforming parser may be limited
+to a certain encoding.
+
+Tabs in lines are expanded to spaces, with a tab stop of 4 characters:
+
+.
+→foo→baz→→bim
+.
+<pre><code>foo baz bim
+</code></pre>
+.
+
+.
+ a→a
+ ὐ→a
+.
+<pre><code>a a
+ὐ a
+</code></pre>
+.
+
+Line endings are replaced by newline characters (LF).
+
+A line containing no characters, or a line containing only spaces (after
+tab expansion), is called a [blank line](#blank-line).
+<a id="blank-line"></a>
+
+# Blocks and inlines
+
+We can think of a document as a sequence of [blocks](#block)<a
+id="block"></a>---structural elements like paragraphs, block quotations,
+lists, headers, rules, and code blocks. Blocks can contain other
+blocks, or they can contain [inline](#inline)<a id="inline"></a> content:
+words, spaces, links, emphasized text, images, and inline code.
+
+## Precedence
+
+Indicators of block structure always take precedence over indicators
+of inline structure. So, for example, the following is a list with
+two items, not a list with one item containing a code span:
+
+.
+- `one
+- two`
+.
+<ul>
+<li>`one</li>
+<li>two`</li>
+</ul>
+.
+
+This means that parsing can proceed in two steps: first, the block
+structure of the document can be discerned; second, text lines inside
+paragraphs, headers, and other block constructs can be parsed for inline
+structure. The second step requires information about link reference
+definitions that will be available only at the end of the first
+step. Note that the first step requires processing lines in sequence,
+but the second can be parallelized, since the inline parsing of
+one block element does not affect the inline parsing of any other.
+
+## Container blocks and leaf blocks
+
+We can divide blocks into two types:
+[container blocks](#container-block), <a id="container-block"></a>
+which can contain other blocks, and [leaf blocks](#leaf-block),
+<a id="leaf-block"></a> which cannot.
+
+# Leaf blocks
+
+This section describes the different kinds of leaf block that make up a
+Markdown document.
+
+## Horizontal rules
+
+A line consisting of 0-3 spaces of indentation, followed by a sequence
+of three or more matching `-`, `_`, or `*` characters, each followed
+optionally by any number of spaces, forms a [horizontal
+rule](#horizontal-rule). <a id="horizontal-rule"></a>
+
+.
+***
+---
+___
+.
+<hr />
+<hr />
+<hr />
+.
+
+Wrong characters:
+
+.
++++
+.
+<p>+++</p>
+.
+
+.
+===
+.
+<p>===</p>
+.
+
+Not enough characters:
+
+.
+--
+**
+__
+.
+<p>--
+**
+__</p>
+.
+
+One to three spaces indent are allowed:
+
+.
+ ***
+ ***
+ ***
+.
+<hr />
+<hr />
+<hr />
+.
+
+Four spaces is too many:
+
+.
+ ***
+.
+<pre><code>***
+</code></pre>
+.
+
+.
+Foo
+ ***
+.
+<p>Foo
+***</p>
+.
+
+More than three characters may be used:
+
+.
+_____________________________________
+.
+<hr />
+.
+
+Spaces are allowed between the characters:
+
+.
+ - - -
+.
+<hr />
+.
+
+.
+ ** * ** * ** * **
+.
+<hr />
+.
+
+.
+- - - -
+.
+<hr />
+.
+
+Spaces are allowed at the end:
+
+.
+- - - -
+.
+<hr />
+.
+
+However, no other characters may occur at the end or the
+beginning:
+
+.
+_ _ _ _ a
+
+a------
+.
+<p>_ _ _ _ a</p>
+<p>a------</p>
+.
+
+It is required that all of the non-space characters be the same.
+So, this is not a horizontal rule:
+
+.
+ *-*
+.
+<p><em>-</em></p>
+.
+
+Horizontal rules do not need blank lines before or after:
+
+.
+- foo
+***
+- bar
+.
+<ul>
+<li>foo</li>
+</ul>
+<hr />
+<ul>
+<li>bar</li>
+</ul>
+.
+
+Horizontal rules can interrupt a paragraph:
+
+.
+Foo
+***
+bar
+.
+<p>Foo</p>
+<hr />
+<p>bar</p>
+.
+
+Note, however, that this is a setext header, not a paragraph followed
+by a horizontal rule:
+
+.
+Foo
+---
+bar
+.
+<h2>Foo</h2>
+<p>bar</p>
+.
+
+When both a horizontal rule and a list item are possible
+interpretations of a line, the horizontal rule is preferred:
+
+.
+* Foo
+* * *
+* Bar
+.
+<ul>
+<li>Foo</li>
+</ul>
+<hr />
+<ul>
+<li>Bar</li>
+</ul>
+.
+
+If you want a horizontal rule in a list item, use a different bullet:
+
+.
+- Foo
+- * * *
+.
+<ul>
+<li>Foo</li>
+<li><hr /></li>
+</ul>
+.
+
+## ATX headers
+
+An [ATX header](#atx-header) <a id="atx-header"></a>
+consists of a string of characters, parsed as inline content, between an
+opening sequence of 1--6 unescaped `#` characters and an optional
+closing sequence of any number of `#` characters. The opening sequence
+of `#` characters cannot be followed directly by a nonspace character.
+The closing `#` characters may be followed by spaces only. The opening
+`#` character may be indented 0-3 spaces. The raw contents of the
+header are stripped of leading and trailing spaces before being parsed
+as inline content. The header level is equal to the number of `#`
+characters in the opening sequence.
+
+Simple headers:
+
+.
+# foo
+## foo
+### foo
+#### foo
+##### foo
+###### foo
+.
+<h1>foo</h1>
+<h2>foo</h2>
+<h3>foo</h3>
+<h4>foo</h4>
+<h5>foo</h5>
+<h6>foo</h6>
+.
+
+More than six `#` characters is not a header:
+
+.
+####### foo
+.
+<p>####### foo</p>
+.
+
+A space is required between the `#` characters and the header's
+contents. Note that many implementations currently do not require
+the space. However, the space was required by the [original ATX
+implementation](http://www.aaronsw.com/2002/atx/atx.py), and it helps
+prevent things like the following from being parsed as headers:
+
+.
+#5 bolt
+.
+<p>#5 bolt</p>
+.
+
+This is not a header, because the first `#` is escaped:
+
+.
+\## foo
+.
+<p>## foo</p>
+.
+
+Contents are parsed as inlines:
+
+.
+# foo *bar* \*baz\*
+.
+<h1>foo <em>bar</em> *baz*</h1>
+.
+
+Leading and trailing blanks are ignored in parsing inline content:
+
+.
+# foo
+.
+<h1>foo</h1>
+.
+
+One to three spaces indentation are allowed:
+
+.
+ ### foo
+ ## foo
+ # foo
+.
+<h3>foo</h3>
+<h2>foo</h2>
+<h1>foo</h1>
+.
+
+Four spaces are too much:
+
+.
+ # foo
+.
+<pre><code># foo
+</code></pre>
+.
+
+.
+foo
+ # bar
+.
+<p>foo
+# bar</p>
+.
+
+A closing sequence of `#` characters is optional:
+
+.
+## foo ##
+ ### bar ###
+.
+<h2>foo</h2>
+<h3>bar</h3>
+.
+
+It need not be the same length as the opening sequence:
+
+.
+# foo ##################################
+##### foo ##
+.
+<h1>foo</h1>
+<h5>foo</h5>
+.
+
+Spaces are allowed after the closing sequence:
+
+.
+### foo ###
+.
+<h3>foo</h3>
+.
+
+A sequence of `#` characters with a nonspace character following it
+is not a closing sequence, but counts as part of the contents of the
+header:
+
+.
+### foo ### b
+.
+<h3>foo ### b</h3>
+.
+
+Backslash-escaped `#` characters do not count as part
+of the closing sequence:
+
+.
+### foo \###
+## foo \#\##
+# foo \#
+.
+<h3>foo #</h3>
+<h2>foo ##</h2>
+<h1>foo #</h1>
+.
+
+ATX headers need not be separated from surrounding content by blank
+lines, and they can interrupt paragraphs:
+
+.
+****
+## foo
+****
+.
+<hr />
+<h2>foo</h2>
+<hr />
+.
+
+.
+Foo bar
+# baz
+Bar foo
+.
+<p>Foo bar</p>
+<h1>baz</h1>
+<p>Bar foo</p>
+.
+
+ATX headers can be empty:
+
+.
+##
+#
+### ###
+.
+<h2></h2>
+<h1></h1>
+<h3></h3>
+.
+
+## Setext headers
+
+A [setext header](#setext-header) <a id="setext-header"></a>
+consists of a line of text, containing at least one nonspace character,
+with no more than 3 spaces indentation, followed by a [setext header
+underline](#setext-header-underline). A [setext header
+underline](#setext-header-underline) <a id="setext-header-underline"></a>
+is a sequence of `=` characters or a sequence of `-` characters, with no
+more than 3 spaces indentation and any number of trailing
+spaces. The header is a level 1 header if `=` characters are used, and
+a level 2 header if `-` characters are used. The contents of the header
+are the result of parsing the first line as Markdown inline content.
+
+In general, a setext header need not be preceded or followed by a
+blank line. However, it cannot interrupt a paragraph, so when a
+setext header comes after a paragraph, a blank line is needed between
+them.
+
+Simple examples:
+
+.
+Foo *bar*
+=========
+
+Foo *bar*
+---------
+.
+<h1>Foo <em>bar</em></h1>
+<h2>Foo <em>bar</em></h2>
+.
+
+The underlining can be any length:
+
+.
+Foo
+-------------------------
+
+Foo
+=
+.
+<h2>Foo</h2>
+<h1>Foo</h1>
+.
+
+The header content can be indented up to three spaces, and need
+not line up with the underlining:
+
+.
+ Foo
+---
+
+ Foo
+-----
+
+ Foo
+ ===
+.
+<h2>Foo</h2>
+<h2>Foo</h2>
+<h1>Foo</h1>
+.
+
+Four spaces indent is too much:
+
+.
+ Foo
+ ---
+
+ Foo
+---
+.
+<pre><code>Foo
+---
+
+Foo
+</code></pre>
+<hr />
+.
+
+The setext header underline can be indented up to three spaces, and
+may have trailing spaces:
+
+.
+Foo
+ ----
+.
+<h2>Foo</h2>
+.
+
+Four spaces is too much:
+
+.
+Foo
+ ---
+.
+<p>Foo
+---</p>
+.
+
+The setext header underline cannot contain internal spaces:
+
+.
+Foo
+= =
+
+Foo
+--- -
+.
+<p>Foo
+= =</p>
+<p>Foo</p>
+<hr />
+.
+
+Trailing spaces in the content line do not cause a line break:
+
+.
+Foo
+-----
+.
+<h2>Foo</h2>
+.
+
+Nor does a backslash at the end:
+
+.
+Foo\
+----
+.
+<h2>Foo\</h2>
+.
+
+Since indicators of block structure take precedence over
+indicators of inline structure, the following are setext headers:
+
+.
+`Foo
+----
+`
+
+<a title="a lot
+---
+of dashes"/>
+.
+<h2>`Foo</h2>
+<p>`</p>
+<h2>&lt;a title=&quot;a lot</h2>
+<p>of dashes&quot;/&gt;</p>
+.
+
+The setext header underline cannot be a lazy line:
+
+.
+> Foo
+---
+.
+<blockquote>
+<p>Foo</p>
+</blockquote>
+<hr />
+.
+
+A setext header cannot interrupt a paragraph:
+
+.
+Foo
+Bar
+---
+
+Foo
+Bar
+===
+.
+<p>Foo
+Bar</p>
+<hr />
+<p>Foo
+Bar
+===</p>
+.
+
+But in general a blank line is not required before or after:
+
+.
+---
+Foo
+---
+Bar
+---
+Baz
+.
+<hr />
+<h2>Foo</h2>
+<h2>Bar</h2>
+<p>Baz</p>
+.
+
+Setext headers cannot be empty:
+
+.
+
+====
+.
+<p>====</p>
+.
+
+
+## Indented code blocks
+
+An [indented code block](#indented-code-block)
+<a id="indented-code-block"></a> is composed of one or more
+[indented chunks](#indented-chunk) separated by blank lines.
+An [indented chunk](#indented-chunk) <a id="indented-chunk"></a>
+is a sequence of non-blank lines, each indented four or more
+spaces. An indented code block cannot interrupt a paragraph, so
+if it occurs before or after a paragraph, there must be an
+intervening blank line. The contents of the code block are
+the literal contents of the lines, including trailing newlines,
+minus four spaces of indentation. An indented code block has no
+attributes.
+
+.
+ a simple
+ indented code block
+.
+<pre><code>a simple
+ indented code block
+</code></pre>
+.
+
+The contents are literal text, and do not get parsed as Markdown:
+
+.
+ <a/>
+ *hi*
+
+ - one
+.
+<pre><code>&lt;a/&gt;
+*hi*
+
+- one
+</code></pre>
+.
+
+Here we have three chunks separated by blank lines:
+
+.
+ chunk1
+
+ chunk2
+
+
+
+ chunk3
+.
+<pre><code>chunk1
+
+chunk2
+
+
+
+chunk3
+</code></pre>
+.
+
+Any initial spaces beyond four will be included in the content, even
+in interior blank lines:
+
+.
+ chunk1
+
+ chunk2
+.
+<pre><code>chunk1
+
+ chunk2
+</code></pre>
+.
+
+An indented code block cannot interrupt a paragraph. (This
+allows hanging indents and the like.)
+
+.
+Foo
+ bar
+
+.
+<p>Foo
+bar</p>
+.
+
+However, any non-blank line with fewer than four leading spaces ends
+the code block immediately. So a paragraph may occur immediately
+after indented code:
+
+.
+ foo
+bar
+.
+<pre><code>foo
+</code></pre>
+<p>bar</p>
+.
+
+And indented code can occur immediately before and after other kinds of
+blocks:
+
+.
+# Header
+ foo
+Header
+------
+ foo
+----
+.
+<h1>Header</h1>
+<pre><code>foo
+</code></pre>
+<h2>Header</h2>
+<pre><code>foo
+</code></pre>
+<hr />
+.
+
+The first line can be indented more than four spaces:
+
+.
+ foo
+ bar
+.
+<pre><code> foo
+bar
+</code></pre>
+.
+
+Blank lines preceding or following an indented code block
+are not included in it:
+
+.
+
+
+ foo
+
+
+.
+<pre><code>foo
+</code></pre>
+.
+
+Trailing spaces are included in the code block's content:
+
+.
+ foo
+.
+<pre><code>foo
+</code></pre>
+.
+
+
+## Fenced code blocks
+
+A [code fence](#code-fence) <a id="code-fence"></a> is a sequence
+of at least three consecutive backtick characters (`` ` ``) or
+tildes (`~`). (Tildes and backticks cannot be mixed.)
+A [fenced code block](#fenced-code-block) <a id="fenced-code-block"></a>
+begins with a code fence, indented no more than three spaces.
+
+The line with the opening code fence may optionally contain some text
+following the code fence; this is trimmed of leading and trailing
+spaces and called the [info string](#info-string).
+<a id="info-string"></a> The info string may not contain any backtick
+characters. (The reason for this restriction is that otherwise
+some inline code would be incorrectly interpreted as the
+beginning of a fenced code block.)
+
+The content of the code block consists of all subsequent lines, until
+a closing [code fence](#code-fence) of the same type as the code block
+began with (backticks or tildes), and with at least as many backticks
+or tildes as the opening code fence. If the leading code fence is
+indented N spaces, then up to N spaces of indentation are removed from
+each line of the content (if present). (If a content line is not
+indented, it is preserved unchanged. If it is indented less than N
+spaces, all of the indentation is removed.)
+
+The closing code fence may be indented up to three spaces, and may be
+followed only by spaces, which are ignored. If the end of the
+containing block (or document) is reached and no closing code fence
+has been found, the code block contains all of the lines after the
+opening code fence until the end of the containing block (or
+document). (An alternative spec would require backtracking in the
+event that a closing code fence is not found. But this makes parsing
+much less efficient, and there seems to be no real down side to the
+behavior described here.)
+
+A fenced code block may interrupt a paragraph, and does not require
+a blank line either before or after.
+
+The content of a code fence is treated as literal text, not parsed
+as inlines. The first word of the info string is typically used to
+specify the language of the code sample, and rendered in the `class`
+attribute of the `code` tag. However, this spec does not mandate any
+particular treatment of the info string.
+
+Here is a simple example with backticks:
+
+.
+```
+<
+ >
+```
+.
+<pre><code>&lt;
+ &gt;
+</code></pre>
+.
+
+With tildes:
+
+.
+~~~
+<
+ >
+~~~
+.
+<pre><code>&lt;
+ &gt;
+</code></pre>
+.
+
+The closing code fence must use the same character as the opening
+fence:
+
+.
+```
+aaa
+~~~
+```
+.
+<pre><code>aaa
+~~~
+</code></pre>
+.
+
+.
+~~~
+aaa
+```
+~~~
+.
+<pre><code>aaa
+```
+</code></pre>
+.
+
+The closing code fence must be at least as long as the opening fence:
+
+.
+````
+aaa
+```
+``````
+.
+<pre><code>aaa
+```
+</code></pre>
+.
+
+.
+~~~~
+aaa
+~~~
+~~~~
+.
+<pre><code>aaa
+~~~
+</code></pre>
+.
+
+Unclosed code blocks are closed by the end of the document:
+
+.
+```
+.
+<pre><code></code></pre>
+.
+
+.
+`````
+
+```
+aaa
+.
+<pre><code>
+```
+aaa
+</code></pre>
+.
+
+A code block can have all empty lines as its content:
+
+.
+```
+
+
+```
+.
+<pre><code>
+
+</code></pre>
+.
+
+A code block can be empty:
+
+.
+```
+```
+.
+<pre><code></code></pre>
+.
+
+Fences can be indented. If the opening fence is indented,
+content lines will have equivalent opening indentation removed,
+if present:
+
+.
+ ```
+ aaa
+aaa
+```
+.
+<pre><code>aaa
+aaa
+</code></pre>
+.
+
+.
+ ```
+aaa
+ aaa
+aaa
+ ```
+.
+<pre><code>aaa
+aaa
+aaa
+</code></pre>
+.
+
+.
+ ```
+ aaa
+ aaa
+ aaa
+ ```
+.
+<pre><code>aaa
+ aaa
+aaa
+</code></pre>
+.
+
+Four spaces indentation produces an indented code block:
+
+.
+ ```
+ aaa
+ ```
+.
+<pre><code>```
+aaa
+```
+</code></pre>
+.
+
+Code fences (opening and closing) cannot contain internal spaces:
+
+.
+``` ```
+aaa
+.
+<p><code></code>
+aaa</p>
+.
+
+.
+~~~~~~
+aaa
+~~~ ~~
+.
+<pre><code>aaa
+~~~ ~~
+</code></pre>
+.
+
+Fenced code blocks can interrupt paragraphs, and can be followed
+directly by paragraphs, without a blank line between:
+
+.
+foo
+```
+bar
+```
+baz
+.
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+.
+
+Other blocks can also occur before and after fenced code blocks
+without an intervening blank line:
+
+.
+foo
+---
+~~~
+bar
+~~~
+# baz
+.
+<h2>foo</h2>
+<pre><code>bar
+</code></pre>
+<h1>baz</h1>
+.
+
+An [info string](#info-string) can be provided after the opening code fence.
+Opening and closing spaces will be stripped, and the first word, prefixed
+with `language-`, is used as the value for the `class` attribute of the
+`code` element within the enclosing `pre` element.
+
+.
+```ruby
+def foo(x)
+ return 3
+end
+```
+.
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+.
+
+.
+~~~~ ruby startline=3 $%@#$
+def foo(x)
+ return 3
+end
+~~~~~~~
+.
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+.
+
+.
+````;
+````
+.
+<pre><code class="language-;"></code></pre>
+.
+
+Info strings for backtick code blocks cannot contain backticks:
+
+.
+``` aa ```
+foo
+.
+<p><code>aa</code>
+foo</p>
+.
+
+Closing code fences cannot have info strings:
+
+.
+```
+``` aaa
+```
+.
+<pre><code>``` aaa
+</code></pre>
+.
+
+
+## HTML blocks
+
+An [HTML block tag](#html-block-tag) <a id="html-block-tag"></a> is
+an [open tag](#open-tag) or [closing tag](#closing-tag) whose tag
+name is one of the following (case-insensitive):
+`article`, `header`, `aside`, `hgroup`, `blockquote`, `hr`, `iframe`,
+`body`, `li`, `map`, `button`, `object`, `canvas`, `ol`, `caption`,
+`output`, `col`, `p`, `colgroup`, `pre`, `dd`, `progress`, `div`,
+`section`, `dl`, `table`, `td`, `dt`, `tbody`, `embed`, `textarea`,
+`fieldset`, `tfoot`, `figcaption`, `th`, `figure`, `thead`, `footer`,
+`footer`, `tr`, `form`, `ul`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`,
+`video`, `script`, `style`.
+
+An [HTML block](#html-block) <a id="html-block"></a> begins with an
+[HTML block tag](#html-block-tag), [HTML comment](#html-comment),
+[processing instruction](#processing-instruction),
+[declaration](#declaration), or [CDATA section](#cdata-section).
+It ends when a [blank line](#blank-line) or the end of the
+input is encountered. The initial line may be indented up to three
+spaces, and subsequent lines may have any indentation. The contents
+of the HTML block are interpreted as raw HTML, and will not be escaped
+in HTML output.
+
+Some simple examples:
+
+.
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+
+okay.
+.
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+<p>okay.</p>
+.
+
+.
+ <div>
+ *hello*
+ <foo><a>
+.
+ <div>
+ *hello*
+ <foo><a>
+.
+
+Here we have two code blocks with a Markdown paragraph between them:
+
+.
+<DIV CLASS="foo">
+
+*Markdown*
+
+</DIV>
+.
+<DIV CLASS="foo">
+<p><em>Markdown</em></p>
+</DIV>
+.
+
+In the following example, what looks like a Markdown code block
+is actually part of the HTML block, which continues until a blank
+line or the end of the document is reached:
+
+.
+<div></div>
+``` c
+int x = 33;
+```
+.
+<div></div>
+``` c
+int x = 33;
+```
+.
+
+A comment:
+
+.
+<!-- Foo
+bar
+ baz -->
+.
+<!-- Foo
+bar
+ baz -->
+.
+
+A processing instruction:
+
+.
+<?php
+ echo 'foo'
+?>
+.
+<?php
+ echo 'foo'
+?>
+.
+
+CDATA:
+
+.
+<![CDATA[
+function matchwo(a,b)
+{
+if (a < b && a < 0) then
+ {
+ return 1;
+ }
+else
+ {
+ return 0;
+ }
+}
+]]>
+.
+<![CDATA[
+function matchwo(a,b)
+{
+if (a < b && a < 0) then
+ {
+ return 1;
+ }
+else
+ {
+ return 0;
+ }
+}
+]]>
+.
+
+The opening tag can be indented 1-3 spaces, but not 4:
+
+.
+ <!-- foo -->
+
+ <!-- foo -->
+.
+ <!-- foo -->
+<pre><code>&lt;!-- foo --&gt;
+</code></pre>
+.
+
+An HTML block can interrupt a paragraph, and need not be preceded
+by a blank line.
+
+.
+Foo
+<div>
+bar
+</div>
+.
+<p>Foo</p>
+<div>
+bar
+</div>
+.
+
+However, a following blank line is always needed, except at the end of
+a document:
+
+.
+<div>
+bar
+</div>
+*foo*
+.
+<div>
+bar
+</div>
+*foo*
+.
+
+An incomplete HTML block tag may also start an HTML block:
+
+.
+<div class
+foo
+.
+<div class
+foo
+.
+
+This rule differs from John Gruber's original Markdown syntax
+specification, which says:
+
+> The only restrictions are that block-level HTML elements —
+> e.g. `<div>`, `<table>`, `<pre>`, `<p>`, etc. — must be separated from
+> surrounding content by blank lines, and the start and end tags of the
+> block should not be indented with tabs or spaces.
+
+In some ways Gruber's rule is more restrictive than the one given
+here:
+
+- It requires that an HTML block be preceded by a blank line.
+- It does not allow the start tag to be indented.
+- It requires a matching end tag, which it also does not allow to
+ be indented.
+
+Indeed, most Markdown implementations, including some of Gruber's
+own perl implementations, do not impose these restrictions.
+
+There is one respect, however, in which Gruber's rule is more liberal
+than the one given here, since it allows blank lines to occur inside
+an HTML block. There are two reasons for disallowing them here.
+First, it removes the need to parse balanced tags, which is
+expensive and can require backtracking from the end of the document
+if no matching end tag is found. Second, it provides a very simple
+and flexible way of including Markdown content inside HTML tags:
+simply separate the Markdown from the HTML using blank lines:
+
+.
+<div>
+
+*Emphasized* text.
+
+</div>
+.
+<div>
+<p><em>Emphasized</em> text.</p>
+</div>
+.
+
+Compare:
+
+.
+<div>
+*Emphasized* text.
+</div>
+.
+<div>
+*Emphasized* text.
+</div>
+.
+
+Some Markdown implementations have adopted a convention of
+interpreting content inside tags as text if the open tag has
+the attribute `markdown=1`. The rule given above seems a simpler and
+more elegant way of achieving the same expressive power, which is also
+much simpler to parse.
+
+The main potential drawback is that one can no longer paste HTML
+blocks into Markdown documents with 100% reliability. However,
+*in most cases* this will work fine, because the blank lines in
+HTML are usually followed by HTML block tags. For example:
+
+.
+<table>
+
+<tr>
+
+<td>
+Hi
+</td>
+
+</tr>
+
+</table>
+.
+<table>
+<tr>
+<td>
+Hi
+</td>
+</tr>
+</table>
+.
+
+Moreover, blank lines are usually not necessary and can be
+deleted. The exception is inside `<pre>` tags; here, one can
+replace the blank lines with `&#10;` entities.
+
+So there is no important loss of expressive power with the new rule.
+
+## Link reference definitions
+
+A [link reference definition](#link-reference-definition)
+<a id="link-reference-definition"></a> consists of a [link
+label](#link-label), indented up to three spaces, followed
+by a colon (`:`), optional blank space (including up to one
+newline), a [link destination](#link-destination), optional
+blank space (including up to one newline), and an optional [link
+title](#link-title), which if it is present must be separated
+from the [link destination](#link-destination) by whitespace.
+No further non-space characters may occur on the line.
+
+A [link reference-definition](#link-reference-definition)
+does not correspond to a structural element of a document. Instead, it
+defines a label which can be used in [reference links](#reference-link)
+and reference-style [images](#image) elsewhere in the document. [Link
+reference definitions] can come either before or after the links that use
+them.
+
+.
+[foo]: /url "title"
+
+[foo]
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+.
+ [foo]:
+ /url
+ 'the title'
+
+[foo]
+.
+<p><a href="/url" title="the title">foo</a></p>
+.
+
+.
+[Foo*bar\]]:my_(url) 'title (with parens)'
+
+[Foo*bar\]]
+.
+<p><a href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
+.
+
+.
+[Foo bar]:
+<my url>
+'title'
+
+[Foo bar]
+.
+<p><a href="my%20url" title="title">Foo bar</a></p>
+.
+
+The title may be omitted:
+
+.
+[foo]:
+/url
+
+[foo]
+.
+<p><a href="/url">foo</a></p>
+.
+
+The link destination may not be omitted:
+
+.
+[foo]:
+
+[foo]
+.
+<p>[foo]:</p>
+<p>[foo]</p>
+.
+
+A link can come before its corresponding definition:
+
+.
+[foo]
+
+[foo]: url
+.
+<p><a href="url">foo</a></p>
+.
+
+If there are several matching definitions, the first one takes
+precedence:
+
+.
+[foo]
+
+[foo]: first
+[foo]: second
+.
+<p><a href="first">foo</a></p>
+.
+
+As noted in the section on [Links], matching of labels is
+case-insensitive (see [matches](#matches)).
+
+.
+[FOO]: /url
+
+[Foo]
+.
+<p><a href="/url">Foo</a></p>
+.
+
+.
+[ΑΓΩ]: /φου
+
+[αγω]
+.
+<p><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
+.
+
+Here is a link reference definition with no corresponding link.
+It contributes nothing to the document.
+
+.
+[foo]: /url
+.
+.
+
+This is not a link reference definition, because there are
+non-space characters after the title:
+
+.
+[foo]: /url "title" ok
+.
+<p>[foo]: /url &quot;title&quot; ok</p>
+.
+
+This is not a link reference definition, because it is indented
+four spaces:
+
+.
+ [foo]: /url "title"
+
+[foo]
+.
+<pre><code>[foo]: /url &quot;title&quot;
+</code></pre>
+<p>[foo]</p>
+.
+
+This is not a link reference definition, because it occurs inside
+a code block:
+
+.
+```
+[foo]: /url
+```
+
+[foo]
+.
+<pre><code>[foo]: /url
+</code></pre>
+<p>[foo]</p>
+.
+
+A [link reference definition](#link-reference-definition) cannot
+interrupt a paragraph.
+
+.
+Foo
+[bar]: /baz
+
+[bar]
+.
+<p>Foo
+[bar]: /baz</p>
+<p>[bar]</p>
+.
+
+However, it can directly follow other block elements, such as headers
+and horizontal rules, and it need not be followed by a blank line.
+
+.
+# [Foo]
+[foo]: /url
+> bar
+.
+<h1><a href="/url">Foo</a></h1>
+<blockquote>
+<p>bar</p>
+</blockquote>
+.
+
+Several [link references](#link-reference) can occur one after another,
+without intervening blank lines.
+
+.
+[foo]: /foo-url "foo"
+[bar]: /bar-url
+ "bar"
+[baz]: /baz-url
+
+[foo],
+[bar],
+[baz]
+.
+<p><a href="/foo-url" title="foo">foo</a>,
+<a href="/bar-url" title="bar">bar</a>,
+<a href="/baz-url">baz</a></p>
+.
+
+[Link reference definitions](#link-reference-definition) can occur
+inside block containers, like lists and block quotations. They
+affect the entire document, not just the container in which they
+are defined:
+
+.
+[foo]
+
+> [foo]: /url
+.
+<p><a href="/url">foo</a></p>
+<blockquote>
+</blockquote>
+.
+
+
+## Paragraphs
+
+A sequence of non-blank lines that cannot be interpreted as other
+kinds of blocks forms a [paragraph](#paragraph).<a id="paragraph"></a>
+The contents of the paragraph are the result of parsing the
+paragraph's raw content as inlines. The paragraph's raw content
+is formed by concatenating the lines and removing initial and final
+spaces.
+
+A simple example with two paragraphs:
+
+.
+aaa
+
+bbb
+.
+<p>aaa</p>
+<p>bbb</p>
+.
+
+Paragraphs can contain multiple lines, but no blank lines:
+
+.
+aaa
+bbb
+
+ccc
+ddd
+.
+<p>aaa
+bbb</p>
+<p>ccc
+ddd</p>
+.
+
+Multiple blank lines between paragraph have no effect:
+
+.
+aaa
+
+
+bbb
+.
+<p>aaa</p>
+<p>bbb</p>
+.
+
+Leading spaces are skipped:
+
+.
+ aaa
+ bbb
+.
+<p>aaa
+bbb</p>
+.
+
+Lines after the first may be indented any amount, since indented
+code blocks cannot interrupt paragraphs.
+
+.
+aaa
+ bbb
+ ccc
+.
+<p>aaa
+bbb
+ccc</p>
+.
+
+However, the first line may be indented at most three spaces,
+or an indented code block will be triggered:
+
+.
+ aaa
+bbb
+.
+<p>aaa
+bbb</p>
+.
+
+.
+ aaa
+bbb
+.
+<pre><code>aaa
+</code></pre>
+<p>bbb</p>
+.
+
+Final spaces are stripped before inline parsing, so a paragraph
+that ends with two or more spaces will not end with a hard line
+break:
+
+.
+aaa
+bbb
+.
+<p>aaa<br />
+bbb</p>
+.
+
+## Blank lines
+
+[Blank lines](#blank-line) between block-level elements are ignored,
+except for the role they play in determining whether a [list](#list)
+is [tight](#tight) or [loose](#loose).
+
+Blank lines at the beginning and end of the document are also ignored.
+
+.
+
+
+aaa
+
+
+# aaa
+
+
+.
+<p>aaa</p>
+<h1>aaa</h1>
+.
+
+
+# Container blocks
+
+A [container block](#container-block) is a block that has other
+blocks as its contents. There are two basic kinds of container blocks:
+[block quotes](#block-quote) and [list items](#list-item).
+[Lists](#list) are meta-containers for [list items](#list-item).
+
+We define the syntax for container blocks recursively. The general
+form of the definition is:
+
+> If X is a sequence of blocks, then the result of
+> transforming X in such-and-such a way is a container of type Y
+> with these blocks as its content.
+
+So, we explain what counts as a block quote or list item by explaining
+how these can be *generated* from their contents. This should suffice
+to define the syntax, although it does not give a recipe for *parsing*
+these constructions. (A recipe is provided below in the section entitled
+[A parsing strategy](#appendix-a-a-parsing-strategy).)
+
+## Block quotes
+
+A [block quote marker](#block-quote-marker) <a id="block-quote-marker"></a>
+consists of 0-3 spaces of initial indent, plus (a) the character `>` together
+with a following space, or (b) a single character `>` not followed by a space.
+
+The following rules define [block quotes](#block-quote):
+<a id="block-quote"></a>
+
+1. **Basic case.** If a string of lines *Ls* constitute a sequence
+ of blocks *Bs*, then the result of appending a [block quote
+ marker](#block-quote-marker) to the beginning of each line in *Ls*
+ is a [block quote](#block-quote) containing *Bs*.
+
+2. **Laziness.** If a string of lines *Ls* constitute a [block
+ quote](#block-quote) with contents *Bs*, then the result of deleting
+ the initial [block quote marker](#block-quote-marker) from one or
+ more lines in which the next non-space character after the [block
+ quote marker](#block-quote-marker) is [paragraph continuation
+ text](#paragraph-continuation-text) is a block quote with *Bs* as
+ its content. <a id="paragraph-continuation-text"></a>
+ [Paragraph continuation text](#paragraph-continuation-text) is text
+ that will be parsed as part of the content of a paragraph, but does
+ not occur at the beginning of the paragraph.
+
+3. **Consecutiveness.** A document cannot contain two [block
+ quotes](#block-quote) in a row unless there is a [blank
+ line](#blank-line) between them.
+
+Nothing else counts as a [block quote](#block-quote).
+
+Here is a simple example:
+
+.
+> # Foo
+> bar
+> baz
+.
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+.
+
+The spaces after the `>` characters can be omitted:
+
+.
+># Foo
+>bar
+> baz
+.
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+.
+
+The `>` characters can be indented 1-3 spaces:
+
+.
+ > # Foo
+ > bar
+ > baz
+.
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+.
+
+Four spaces gives us a code block:
+
+.
+ > # Foo
+ > bar
+ > baz
+.
+<pre><code>&gt; # Foo
+&gt; bar
+&gt; baz
+</code></pre>
+.
+
+The Laziness clause allows us to omit the `>` before a
+paragraph continuation line:
+
+.
+> # Foo
+> bar
+baz
+.
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+.
+
+A block quote can contain some lazy and some non-lazy
+continuation lines:
+
+.
+> bar
+baz
+> foo
+.
+<blockquote>
+<p>bar
+baz
+foo</p>
+</blockquote>
+.
+
+Laziness only applies to lines that are continuations of
+paragraphs. Lines containing characters or indentation that indicate
+block structure cannot be lazy.
+
+.
+> foo
+---
+.
+<blockquote>
+<p>foo</p>
+</blockquote>
+<hr />
+.
+
+.
+> - foo
+- bar
+.
+<blockquote>
+<ul>
+<li>foo</li>
+</ul>
+</blockquote>
+<ul>
+<li>bar</li>
+</ul>
+.
+
+.
+> foo
+ bar
+.
+<blockquote>
+<pre><code>foo
+</code></pre>
+</blockquote>
+<pre><code>bar
+</code></pre>
+.
+
+.
+> ```
+foo
+```
+.
+<blockquote>
+<pre><code></code></pre>
+</blockquote>
+<p>foo</p>
+<pre><code></code></pre>
+.
+
+A block quote can be empty:
+
+.
+>
+.
+<blockquote>
+</blockquote>
+.
+
+.
+>
+>
+>
+.
+<blockquote>
+</blockquote>
+.
+
+A block quote can have initial or final blank lines:
+
+.
+>
+> foo
+>
+.
+<blockquote>
+<p>foo</p>
+</blockquote>
+.
+
+A blank line always separates block quotes:
+
+.
+> foo
+
+> bar
+.
+<blockquote>
+<p>foo</p>
+</blockquote>
+<blockquote>
+<p>bar</p>
+</blockquote>
+.
+
+(Most current Markdown implementations, including John Gruber's
+original `Markdown.pl`, will parse this example as a single block quote
+with two paragraphs. But it seems better to allow the author to decide
+whether two block quotes or one are wanted.)
+
+Consecutiveness means that if we put these block quotes together,
+we get a single block quote:
+
+.
+> foo
+> bar
+.
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+.
+
+To get a block quote with two paragraphs, use:
+
+.
+> foo
+>
+> bar
+.
+<blockquote>
+<p>foo</p>
+<p>bar</p>
+</blockquote>
+.
+
+Block quotes can interrupt paragraphs:
+
+.
+foo
+> bar
+.
+<p>foo</p>
+<blockquote>
+<p>bar</p>
+</blockquote>
+.
+
+In general, blank lines are not needed before or after block
+quotes:
+
+.
+> aaa
+***
+> bbb
+.
+<blockquote>
+<p>aaa</p>
+</blockquote>
+<hr />
+<blockquote>
+<p>bbb</p>
+</blockquote>
+.
+
+However, because of laziness, a blank line is needed between
+a block quote and a following paragraph:
+
+.
+> bar
+baz
+.
+<blockquote>
+<p>bar
+baz</p>
+</blockquote>
+.
+
+.
+> bar
+
+baz
+.
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+.
+
+.
+> bar
+>
+baz
+.
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+.
+
+It is a consequence of the Laziness rule that any number
+of initial `>`s may be omitted on a continuation line of a
+nested block quote:
+
+.
+> > > foo
+bar
+.
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+</blockquote>
+</blockquote>
+.
+
+.
+>>> foo
+> bar
+>>baz
+.
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar
+baz</p>
+</blockquote>
+</blockquote>
+</blockquote>
+.
+
+When including an indented code block in a block quote,
+remember that the [block quote marker](#block-quote-marker) includes
+both the `>` and a following space. So *five spaces* are needed after
+the `>`:
+
+.
+> code
+
+> not code
+.
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>not code</p>
+</blockquote>
+.
+
+
+## List items
+
+A [list marker](#list-marker) <a id="list-marker"></a> is a
+[bullet list marker](#bullet-list-marker) or an [ordered list
+marker](#ordered-list-marker).
+
+A [bullet list marker](#bullet-list-marker) <a id="bullet-list-marker"></a>
+is a `-`, `+`, or `*` character.
+
+An [ordered list marker](#ordered-list-marker) <a id="ordered-list-marker"></a>
+is a sequence of one of more digits (`0-9`), followed by either a
+`.` character or a `)` character.
+
+The following rules define [list items](#list-item):
+
+1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of
+ blocks *Bs* starting with a non-space character and not separated
+ from each other by more than one blank line, and *M* is a list
+ marker *M* of width *W* followed by 0 < *N* < 5 spaces, then the result
+ of prepending *M* and the following spaces to the first line of
+ *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a
+ list item with *Bs* as its contents. The type of the list item
+ (bullet or ordered) is determined by the type of its list marker.
+ If the list item is ordered, then it is also assigned a start
+ number, based on the ordered list marker.
+
+For example, let *Ls* be the lines
+
+.
+A paragraph
+with two lines.
+
+ indented code
+
+> A block quote.
+.
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+.
+
+And let *M* be the marker `1.`, and *N* = 2. Then rule #1 says
+that the following is an ordered list item with start number 1,
+and the same contents as *Ls*:
+
+.
+1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<ol>
+<li><p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote></li>
+</ol>
+.
+
+The most important thing to notice is that the position of
+the text after the list marker determines how much indentation
+is needed in subsequent blocks in the list item. If the list
+marker takes up two spaces, and there are three spaces between
+the list marker and the next nonspace character, then blocks
+must be indented five spaces in order to fall under the list
+item.
+
+Here are some examples showing how far content must be indented to be
+put under the list item:
+
+.
+- one
+
+ two
+.
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+.
+
+.
+- one
+
+ two
+.
+<ul>
+<li><p>one</p>
+<p>two</p></li>
+</ul>
+.
+
+.
+ - one
+
+ two
+.
+<ul>
+<li>one</li>
+</ul>
+<pre><code> two
+</code></pre>
+.
+
+.
+ - one
+
+ two
+.
+<ul>
+<li><p>one</p>
+<p>two</p></li>
+</ul>
+.
+
+It is tempting to think of this in terms of columns: the continuation
+blocks must be indented at least to the column of the first nonspace
+character after the list marker. However, that is not quite right.
+The spaces after the list marker determine how much relative indentation
+is needed. Which column this indentation reaches will depend on
+how the list item is embedded in other constructions, as shown by
+this example:
+
+.
+ > > 1. one
+>>
+>> two
+.
+<blockquote>
+<blockquote>
+<ol>
+<li><p>one</p>
+<p>two</p></li>
+</ol>
+</blockquote>
+</blockquote>
+.
+
+Here `two` occurs in the same column as the list marker `1.`,
+but is actually contained in the list item, because there is
+sufficent indentation after the last containing blockquote marker.
+
+The converse is also possible. In the following example, the word `two`
+occurs far to the right of the initial text of the list item, `one`, but
+it is not considered part of the list item, because it is not indented
+far enough past the blockquote marker:
+
+.
+>>- one
+>>
+ > > two
+.
+<blockquote>
+<blockquote>
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+</blockquote>
+</blockquote>
+.
+
+A list item may not contain blocks that are separated by more than
+one blank line. Thus, two blank lines will end a list, unless the
+two blanks are contained in a [fenced code block](#fenced-code-block).
+
+.
+- foo
+
+ bar
+
+- foo
+
+
+ bar
+
+- ```
+ foo
+
+
+ bar
+ ```
+.
+<ul>
+<li><p>foo</p>
+<p>bar</p></li>
+<li><p>foo</p></li>
+</ul>
+<p>bar</p>
+<ul>
+<li><pre><code>foo
+
+
+bar
+</code></pre></li>
+</ul>
+.
+
+A list item may contain any kind of block:
+
+.
+1. foo
+
+ ```
+ bar
+ ```
+
+ baz
+
+ > bam
+.
+<ol>
+<li><p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+<blockquote>
+<p>bam</p>
+</blockquote></li>
+</ol>
+.
+
+2. **Item starting with indented code.** If a sequence of lines *Ls*
+ constitute a sequence of blocks *Bs* starting with an indented code
+ block and not separated from each other by more than one blank line,
+ and *M* is a list marker *M* of width *W* followed by
+ one space, then the result of prepending *M* and the following
+ space to the first line of *Ls*, and indenting subsequent lines of
+ *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents.
+ If a line is empty, then it need not be indented. The type of the
+ list item (bullet or ordered) is determined by the type of its list
+ marker. If the list item is ordered, then it is also assigned a
+ start number, based on the ordered list marker.
+
+An indented code block will have to be indented four spaces beyond
+the edge of the region where text will be included in the list item.
+In the following case that is 6 spaces:
+
+.
+- foo
+
+ bar
+.
+<ul>
+<li><p>foo</p>
+<pre><code>bar
+</code></pre></li>
+</ul>
+.
+
+And in this case it is 11 spaces:
+
+.
+ 10. foo
+
+ bar
+.
+<ol start="10">
+<li><p>foo</p>
+<pre><code>bar
+</code></pre></li>
+</ol>
+.
+
+If the *first* block in the list item is an indented code block,
+then by rule #2, the contents must be indented *one* space after the
+list marker:
+
+.
+ indented code
+
+paragraph
+
+ more code
+.
+<pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+.
+
+.
+1. indented code
+
+ paragraph
+
+ more code
+.
+<ol>
+<li><pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre></li>
+</ol>
+.
+
+Note that an additional space indent is interpreted as space
+inside the code block:
+
+.
+1. indented code
+
+ paragraph
+
+ more code
+.
+<ol>
+<li><pre><code> indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre></li>
+</ol>
+.
+
+Note that rules #1 and #2 only apply to two cases: (a) cases
+in which the lines to be included in a list item begin with a nonspace
+character, and (b) cases in which they begin with an indented code
+block. In a case like the following, where the first block begins with
+a three-space indent, the rules do not allow us to form a list item by
+indenting the whole thing and prepending a list marker:
+
+.
+ foo
+
+bar
+.
+<p>foo</p>
+<p>bar</p>
+.
+
+.
+- foo
+
+ bar
+.
+<ul>
+<li>foo</li>
+</ul>
+<p>bar</p>
+.
+
+This is not a significant restriction, because when a block begins
+with 1-3 spaces indent, the indentation can always be removed without
+a change in interpretation, allowing rule #1 to be applied. So, in
+the above case:
+
+.
+- foo
+
+ bar
+.
+<ul>
+<li><p>foo</p>
+<p>bar</p></li>
+</ul>
+.
+
+
+3. **Indentation.** If a sequence of lines *Ls* constitutes a list item
+ according to rule #1 or #2, then the result of indenting each line
+ of *L* by 1-3 spaces (the same for each line) also constitutes a
+ list item with the same contents and attributes. If a line is
+ empty, then it need not be indented.
+
+Indented one space:
+
+.
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<ol>
+<li><p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote></li>
+</ol>
+.
+
+Indented two spaces:
+
+.
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<ol>
+<li><p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote></li>
+</ol>
+.
+
+Indented three spaces:
+
+.
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<ol>
+<li><p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote></li>
+</ol>
+.
+
+Four spaces indent gives a code block:
+
+.
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<pre><code>1. A paragraph
+ with two lines.
+
+ indented code
+
+ &gt; A block quote.
+</code></pre>
+.
+
+
+4. **Laziness.** If a string of lines *Ls* constitute a [list
+ item](#list-item) with contents *Bs*, then the result of deleting
+ some or all of the indentation from one or more lines in which the
+ next non-space character after the indentation is
+ [paragraph continuation text](#paragraph-continuation-text) is a
+ list item with the same contents and attributes.
+
+Here is an example with lazy continuation lines:
+
+.
+ 1. A paragraph
+with two lines.
+
+ indented code
+
+ > A block quote.
+.
+<ol>
+<li><p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote></li>
+</ol>
+.
+
+Indentation can be partially deleted:
+
+.
+ 1. A paragraph
+ with two lines.
+.
+<ol>
+<li>A paragraph
+with two lines.</li>
+</ol>
+.
+
+These examples show how laziness can work in nested structures:
+
+.
+> 1. > Blockquote
+continued here.
+.
+<blockquote>
+<ol>
+<li><blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote></li>
+</ol>
+</blockquote>
+.
+
+.
+> 1. > Blockquote
+> continued here.
+.
+<blockquote>
+<ol>
+<li><blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote></li>
+</ol>
+</blockquote>
+.
+
+
+5. **That's all.** Nothing that is not counted as a list item by rules
+ #1--4 counts as a [list item](#list-item).
+
+The rules for sublists follow from the general rules above. A sublist
+must be indented the same number of spaces a paragraph would need to be
+in order to be included in the list item.
+
+So, in this case we need two spaces indent:
+
+.
+- foo
+ - bar
+ - baz
+.
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz</li>
+</ul></li>
+</ul></li>
+</ul>
+.
+
+One is not enough:
+
+.
+- foo
+ - bar
+ - baz
+.
+<ul>
+<li>foo</li>
+<li>bar</li>
+<li>baz</li>
+</ul>
+.
+
+Here we need four, because the list marker is wider:
+
+.
+10) foo
+ - bar
+.
+<ol start="10">
+<li>foo
+<ul>
+<li>bar</li>
+</ul></li>
+</ol>
+.
+
+Three is not enough:
+
+.
+10) foo
+ - bar
+.
+<ol start="10">
+<li>foo</li>
+</ol>
+<ul>
+<li>bar</li>
+</ul>
+.
+
+A list may be the first block in a list item:
+
+.
+- - foo
+.
+<ul>
+<li><ul>
+<li>foo</li>
+</ul></li>
+</ul>
+.
+
+.
+1. - 2. foo
+.
+<ol>
+<li><ul>
+<li><ol start="2">
+<li>foo</li>
+</ol></li>
+</ul></li>
+</ol>
+.
+
+A list item may be empty:
+
+.
+- foo
+-
+- bar
+.
+<ul>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ul>
+.
+
+.
+-
+.
+<ul>
+<li></li>
+</ul>
+.
+
+### Motivation
+
+John Gruber's Markdown spec says the following about list items:
+
+1. "List markers typically start at the left margin, but may be indented
+ by up to three spaces. List markers must be followed by one or more
+ spaces or a tab."
+
+2. "To make lists look nice, you can wrap items with hanging indents....
+ But if you don't want to, you don't have to."
+
+3. "List items may consist of multiple paragraphs. Each subsequent
+ paragraph in a list item must be indented by either 4 spaces or one
+ tab."
+
+4. "It looks nice if you indent every line of the subsequent paragraphs,
+ but here again, Markdown will allow you to be lazy."
+
+5. "To put a blockquote within a list item, the blockquote's `>`
+ delimiters need to be indented."
+
+6. "To put a code block within a list item, the code block needs to be
+ indented twice — 8 spaces or two tabs."
+
+These rules specify that a paragraph under a list item must be indented
+four spaces (presumably, from the left margin, rather than the start of
+the list marker, but this is not said), and that code under a list item
+must be indented eight spaces instead of the usual four. They also say
+that a block quote must be indented, but not by how much; however, the
+example given has four spaces indentation. Although nothing is said
+about other kinds of block-level content, it is certainly reasonable to
+infer that *all* block elements under a list item, including other
+lists, must be indented four spaces. This principle has been called the
+*four-space rule*.
+
+The four-space rule is clear and principled, and if the reference
+implementation `Markdown.pl` had followed it, it probably would have
+become the standard. However, `Markdown.pl` allowed paragraphs and
+sublists to start with only two spaces indentation, at least on the
+outer level. Worse, its behavior was inconsistent: a sublist of an
+outer-level list needed two spaces indentation, but a sublist of this
+sublist needed three spaces. It is not surprising, then, that different
+implementations of Markdown have developed very different rules for
+determining what comes under a list item. (Pandoc and python-Markdown,
+for example, stuck with Gruber's syntax description and the four-space
+rule, while discount, redcarpet, marked, PHP Markdown, and others
+followed `Markdown.pl`'s behavior more closely.)
+
+Unfortunately, given the divergences between implementations, there
+is no way to give a spec for list items that will be guaranteed not
+to break any existing documents. However, the spec given here should
+correctly handle lists formatted with either the four-space rule or
+the more forgiving `Markdown.pl` behavior, provided they are laid out
+in a way that is natural for a human to read.
+
+The strategy here is to let the width and indentation of the list marker
+determine the indentation necessary for blocks to fall under the list
+item, rather than having a fixed and arbitrary number. The writer can
+think of the body of the list item as a unit which gets indented to the
+right enough to fit the list marker (and any indentation on the list
+marker). (The laziness rule, #4, then allows continuation lines to be
+unindented if needed.)
+
+This rule is superior, we claim, to any rule requiring a fixed level of
+indentation from the margin. The four-space rule is clear but
+unnatural. It is quite unintuitive that
+
+``` markdown
+- foo
+
+ bar
+
+ - baz
+```
+
+should be parsed as two lists with an intervening paragraph,
+
+``` html
+<ul>
+<li>foo</li>
+</ul>
+<p>bar</p>
+<ul>
+<li>baz</li>
+</ul>
+```
+
+as the four-space rule demands, rather than a single list,
+
+``` html
+<ul>
+<li><p>foo</p>
+<p>bar</p>
+<ul>
+<li>baz</li>
+</ul></li>
+</ul>
+```
+
+The choice of four spaces is arbitrary. It can be learned, but it is
+not likely to be guessed, and it trips up beginners regularly.
+
+Would it help to adopt a two-space rule? The problem is that such
+a rule, together with the rule allowing 1--3 spaces indentation of the
+initial list marker, allows text that is indented *less than* the
+original list marker to be included in the list item. For example,
+`Markdown.pl` parses
+
+``` markdown
+ - one
+
+ two
+```
+
+as a single list item, with `two` a continuation paragraph:
+
+``` html
+<ul>
+<li><p>one</p>
+<p>two</p></li>
+</ul>
+```
+
+and similarly
+
+``` markdown
+> - one
+>
+> two
+```
+
+as
+
+``` html
+<blockquote>
+<ul>
+<li><p>one</p>
+<p>two</p></li>
+</ul>
+</blockquote>
+```
+
+This is extremely unintuitive.
+
+Rather than requiring a fixed indent from the margin, we could require
+a fixed indent (say, two spaces, or even one space) from the list marker (which
+may itself be indented). This proposal would remove the last anomaly
+discussed. Unlike the spec presented above, it would count the following
+as a list item with a subparagraph, even though the paragraph `bar`
+is not indented as far as the first paragraph `foo`:
+
+``` markdown
+ 10. foo
+
+ bar
+```
+
+Arguably this text does read like a list item with `bar` as a subparagraph,
+which may count in favor of the proposal. However, on this proposal indented
+code would have to be indented six spaces after the list marker. And this
+would break a lot of existing Markdown, which has the pattern:
+
+``` markdown
+1. foo
+
+ indented code
+```
+
+where the code is indented eight spaces. The spec above, by contrast, will
+parse this text as expected, since the code block's indentation is measured
+from the beginning of `foo`.
+
+The one case that needs special treatment is a list item that *starts*
+with indented code. How much indentation is required in that case, since
+we don't have a "first paragraph" to measure from? Rule #2 simply stipulates
+that in such cases, we require one space indentation from the list marker
+(and then the normal four spaces for the indented code). This will match the
+four-space rule in cases where the list marker plus its initial indentation
+takes four spaces (a common case), but diverge in other cases.
+
+## Lists
+
+A [list](#list) <a id="list"></a> is a sequence of one or more
+list items [of the same type](#of-the-same-type). The list items
+may be separated by single [blank lines](#blank-line), but two
+blank lines end all containing lists.
+
+Two list items are [of the same type](#of-the-same-type)
+<a id="of-the-same-type"></a> if they begin with a [list
+marker](#list-marker) of the same type. Two list markers are of the
+same type if (a) they are bullet list markers using the same character
+(`-`, `+`, or `*`) or (b) they are ordered list numbers with the same
+delimiter (either `.` or `)`).
+
+A list is an [ordered list](#ordered-list) <a id="ordered-list"></a>
+if its constituent list items begin with
+[ordered list markers](#ordered-list-marker), and a [bullet
+list](#bullet-list) <a id="bullet-list"></a> if its constituent list
+items begin with [bullet list markers](#bullet-list-marker).
+
+The [start number](#start-number) <a id="start-number"></a>
+of an [ordered list](#ordered-list) is determined by the list number of
+its initial list item. The numbers of subsequent list items are
+disregarded.
+
+A list is [loose](#loose) if it any of its constituent list items are
+separated by blank lines, or if any of its constituent list items
+directly contain two block-level elements with a blank line between
+them. Otherwise a list is [tight](#tight). (The difference in HTML output
+is that paragraphs in a loose with are wrapped in `<p>` tags, while
+paragraphs in a tight list are not.)
+
+Changing the bullet or ordered list delimiter starts a new list:
+
+.
+- foo
+- bar
++ baz
+.
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<ul>
+<li>baz</li>
+</ul>
+.
+
+.
+1. foo
+2. bar
+3) baz
+.
+<ol>
+<li>foo</li>
+<li>bar</li>
+</ol>
+<ol start="3">
+<li>baz</li>
+</ol>
+.
+
+There can be blank lines between items, but two blank lines end
+a list:
+
+.
+- foo
+
+- bar
+
+
+- baz
+.
+<ul>
+<li><p>foo</p></li>
+<li><p>bar</p></li>
+</ul>
+<ul>
+<li>baz</li>
+</ul>
+.
+
+As illustrated above in the section on [list items](#list-item),
+two blank lines between blocks *within* a list item will also end a
+list:
+
+.
+- foo
+
+
+ bar
+- baz
+.
+<ul>
+<li>foo</li>
+</ul>
+<p>bar</p>
+<ul>
+<li>baz</li>
+</ul>
+.
+
+Indeed, two blank lines will end *all* containing lists:
+
+.
+- foo
+ - bar
+ - baz
+
+
+ bim
+.
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz</li>
+</ul></li>
+</ul></li>
+</ul>
+<pre><code> bim
+</code></pre>
+.
+
+Thus, two blank lines can be used to separate consecutive lists of
+the same type, or to separate a list from an indented code block
+that would otherwise be parsed as a subparagraph of the final list
+item:
+
+.
+- foo
+- bar
+
+
+- baz
+- bim
+.
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<ul>
+<li>baz</li>
+<li>bim</li>
+</ul>
+.
+
+.
+- foo
+
+ notcode
+
+- foo
+
+
+ code
+.
+<ul>
+<li><p>foo</p>
+<p>notcode</p></li>
+<li><p>foo</p></li>
+</ul>
+<pre><code>code
+</code></pre>
+.
+
+List items need not be indented to the same level. The following
+list items will be treated as items at the same list level,
+since none is indented enough to belong to the previous list
+item:
+
+.
+- a
+ - b
+ - c
+ - d
+ - e
+ - f
+- g
+.
+<ul>
+<li>a</li>
+<li>b</li>
+<li>c</li>
+<li>d</li>
+<li>e</li>
+<li>f</li>
+<li>g</li>
+</ul>
+.
+
+This is a loose list, because there is a blank line between
+two of the list items:
+
+.
+- a
+- b
+
+- c
+.
+<ul>
+<li><p>a</p></li>
+<li><p>b</p></li>
+<li><p>c</p></li>
+</ul>
+.
+
+So is this, with a empty second item:
+
+.
+* a
+*
+
+* c
+.
+<ul>
+<li><p>a</p></li>
+<li></li>
+<li><p>c</p></li>
+</ul>
+.
+
+These are loose lists, even though there is no space between the items,
+because one of the items directly contains two block-level elements
+with a blank line between them:
+
+.
+- a
+- b
+
+ c
+- d
+.
+<ul>
+<li><p>a</p></li>
+<li><p>b</p>
+<p>c</p></li>
+<li><p>d</p></li>
+</ul>
+.
+
+.
+- a
+- b
+
+ [ref]: /url
+- d
+.
+<ul>
+<li><p>a</p></li>
+<li><p>b</p></li>
+<li><p>d</p></li>
+</ul>
+.
+
+This is a tight list, because the blank lines are in a code block:
+
+.
+- a
+- ```
+ b
+
+
+ ```
+- c
+.
+<ul>
+<li>a</li>
+<li><pre><code>b
+
+
+</code></pre></li>
+<li>c</li>
+</ul>
+.
+
+This is a tight list, because the blank line is between two
+paragraphs of a sublist. So the inner list is loose while
+the other list is tight:
+
+.
+- a
+ - b
+
+ c
+- d
+.
+<ul>
+<li>a
+<ul>
+<li><p>b</p>
+<p>c</p></li>
+</ul></li>
+<li>d</li>
+</ul>
+.
+
+This is a tight list, because the blank line is inside the
+block quote:
+
+.
+* a
+ > b
+ >
+* c
+.
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote></li>
+<li>c</li>
+</ul>
+.
+
+This list is tight, because the consecutive block elements
+are not separated by blank lines:
+
+.
+- a
+ > b
+ ```
+ c
+ ```
+- d
+.
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote>
+<pre><code>c
+</code></pre></li>
+<li>d</li>
+</ul>
+.
+
+A single-paragraph list is tight:
+
+.
+- a
+.
+<ul>
+<li>a</li>
+</ul>
+.
+
+.
+- a
+ - b
+.
+<ul>
+<li>a
+<ul>
+<li>b</li>
+</ul></li>
+</ul>
+.
+
+Here the outer list is loose, the inner list tight:
+
+.
+* foo
+ * bar
+
+ baz
+.
+<ul>
+<li><p>foo</p>
+<ul>
+<li>bar</li>
+</ul>
+<p>baz</p></li>
+</ul>
+.
+
+.
+- a
+ - b
+ - c
+
+- d
+ - e
+ - f
+.
+<ul>
+<li><p>a</p>
+<ul>
+<li>b</li>
+<li>c</li>
+</ul></li>
+<li><p>d</p>
+<ul>
+<li>e</li>
+<li>f</li>
+</ul></li>
+</ul>
+.
+
+# Inlines
+
+Inlines are parsed sequentially from the beginning of the character
+stream to the end (left to right, in left-to-right languages).
+Thus, for example, in
+
+.
+`hi`lo`
+.
+<p><code>hi</code>lo`</p>
+.
+
+`hi` is parsed as code, leaving the backtick at the end as a literal
+backtick.
+
+## Backslash escapes
+
+Any ASCII punctuation character may be backslash-escaped:
+
+.
+\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
+.
+<p>!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>
+.
+
+Backslashes before other characters are treated as literal
+backslashes:
+
+.
+\→\A\a\ \3\φ\«
+.
+<p>\ \A\a\ \3\φ\«</p>
+.
+
+Escaped characters are treated as regular characters and do
+not have their usual Markdown meanings:
+
+.
+\*not emphasized*
+\<br/> not a tag
+\[not a link](/foo)
+\`not code`
+1\. not a list
+\* not a list
+\# not a header
+\[foo]: /url "not a reference"
+.
+<p>*not emphasized*
+&lt;br/&gt; not a tag
+[not a link](/foo)
+`not code`
+1. not a list
+* not a list
+# not a header
+[foo]: /url &quot;not a reference&quot;</p>
+.
+
+If a backslash is itself escaped, the following character is not:
+
+.
+\\*emphasis*
+.
+<p>\<em>emphasis</em></p>
+.
+
+A backslash at the end of the line is a hard line break:
+
+.
+foo\
+bar
+.
+<p>foo<br />
+bar</p>
+.
+
+Backslash escapes do not work in code blocks, code spans, autolinks, or
+raw HTML:
+
+.
+`` \[\` ``
+.
+<p><code>\[\`</code></p>
+.
+
+.
+ \[\]
+.
+<pre><code>\[\]
+</code></pre>
+.
+
+.
+~~~
+\[\]
+~~~
+.
+<pre><code>\[\]
+</code></pre>
+.
+
+.
+<http://google.com?find=\*>
+.
+<p><a href="http://google.com?find=%5C*">http://google.com?find=\*</a></p>
+.
+
+.
+<a href="/bar\/)">
+.
+<p><a href="/bar\/)"></p>
+.
+
+But they work in all other contexts, including URLs and link titles,
+link references, and info strings in [fenced code
+blocks](#fenced-code-block):
+
+.
+[foo](/bar\* "ti\*tle")
+.
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+.
+
+.
+[foo]
+
+[foo]: /bar\* "ti\*tle"
+.
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+.
+
+.
+``` foo\+bar
+foo
+```
+.
+<pre><code class="language-foo+bar">foo
+</code></pre>
+.
+
+
+## Entities
+
+With the goal of making this standard as HTML-agnostic as possible, all HTML valid HTML Entities in any
+context are recognized as such and converted into their actual values (i.e. the UTF8 characters representing
+the entity itself) before they are stored in the AST.
+
+This allows implementations that target HTML output to trivially escape the entities when generating HTML,
+and simplifies the job of implementations targetting other languages, as these will only need to handle the
+UTF8 chars and need not be HTML-entity aware.
+
+[Named entities](#name-entities) <a id="named-entities"></a> consist of `&`
++ any of the valid HTML5 entity names + `;`. The [following document](http://www.whatwg.org/specs/web-apps/current-work/multipage/entities.json)
+is used as an authoritative source of the valid entity names and their corresponding codepoints.
+
+Conforming implementations that target Markdown don't need to generate entities for all the valid
+named entities that exist, with the exception of `"` (`&quot;`), `&` (`&amp;`), `<` (`&lt;`) and `>` (`&gt;`),
+which always need to be written as entities for security reasons.
+
+.
+&nbsp; &amp; &copy; &AElig; &Dcaron; &frac34; &HilbertSpace; &DifferentialD; &ClockwiseContourIntegral;
+.
+<p>  &amp; © Æ Ď ¾ ℋ ⅆ ∲</p>
+.
+
+[Decimal entities](#decimal-entities) <a id="decimal-entities"></a>
+consist of `&#` + a string of 1--8 arabic digits + `;`. Again, these entities need to be recognised
+and tranformed into their corresponding UTF8 codepoints. Invalid Unicode codepoints will be written
+as the "unknown codepoint" character (`0xFFFD`)
+
+.
+&#35; &#1234; &#992; &#98765432;
+.
+<p># Ӓ Ϡ �</p>
+.
+
+[Hexadecimal entities](#hexadecimal-entities) <a id="hexadecimal-entities"></a>
+consist of `&#` + either `X` or `x` + a string of 1-8 hexadecimal digits
++ `;`. They will also be parsed and turned into their corresponding UTF8 values in the AST.
+
+.
+&#X22; &#XD06; &#xcab;
+.
+<p>&quot; ആ ಫ</p>
+.
+
+Here are some nonentities:
+
+.
+&nbsp &x; &#; &#x; &ThisIsWayTooLongToBeAnEntityIsntIt; &hi?;
+.
+<p>&amp;nbsp &amp;x; &amp;#; &amp;#x; &amp;ThisIsWayTooLongToBeAnEntityIsntIt; &amp;hi?;</p>
+.
+
+Although HTML5 does accept some entities without a trailing semicolon
+(such as `&copy`), these are not recognized as entities here, because it makes the grammar too ambiguous:
+
+.
+&copy
+.
+<p>&amp;copy</p>
+.
+
+Strings that are not on the list of HTML5 named entities are not recognized as entities either:
+
+.
+&MadeUpEntity;
+.
+<p>&amp;MadeUpEntity;</p>
+.
+
+Entities are recognized in any context besides code spans or
+code blocks, including raw HTML, URLs, [link titles](#link-title), and
+[fenced code block](#fenced-code-block) info strings:
+
+.
+<a href="&ouml;&ouml;.html">
+.
+<p><a href="&ouml;&ouml;.html"></p>
+.
+
+.
+[foo](/f&ouml;&ouml; "f&ouml;&ouml;")
+.
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+.
+
+.
+[foo]
+
+[foo]: /f&ouml;&ouml; "f&ouml;&ouml;"
+.
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+.
+
+.
+``` f&ouml;&ouml;
+foo
+```
+.
+<pre><code class="language-föö">foo
+</code></pre>
+.
+
+Entities are treated as literal text in code spans and code blocks:
+
+.
+`f&ouml;&ouml;`
+.
+<p><code>f&amp;ouml;&amp;ouml;</code></p>
+.
+
+.
+ f&ouml;f&ouml;
+.
+<pre><code>f&amp;ouml;f&amp;ouml;
+</code></pre>
+.
+
+## Code span
+
+A [backtick string](#backtick-string) <a id="backtick-string"></a>
+is a string of one or more backtick characters (`` ` ``) that is neither
+preceded nor followed by a backtick.
+
+A code span begins with a backtick string and ends with a backtick
+string of equal length. The contents of the code span are the
+characters between the two backtick strings, with leading and trailing
+spaces and newlines removed, and consecutive spaces and newlines
+collapsed to single spaces.
+
+This is a simple code span:
+
+.
+`foo`
+.
+<p><code>foo</code></p>
+.
+
+Here two backticks are used, because the code contains a backtick.
+This example also illustrates stripping of leading and trailing spaces:
+
+.
+`` foo ` bar ``
+.
+<p><code>foo ` bar</code></p>
+.
+
+This example shows the motivation for stripping leading and trailing
+spaces:
+
+.
+` `` `
+.
+<p><code>``</code></p>
+.
+
+Newlines are treated like spaces:
+
+.
+``
+foo
+``
+.
+<p><code>foo</code></p>
+.
+
+Interior spaces and newlines are collapsed into single spaces, just
+as they would be by a browser:
+
+.
+`foo bar
+ baz`
+.
+<p><code>foo bar baz</code></p>
+.
+
+Q: Why not just leave the spaces, since browsers will collapse them
+anyway? A: Because we might be targeting a non-HTML format, and we
+shouldn't rely on HTML-specific rendering assumptions.
+
+(Existing implementations differ in their treatment of internal
+spaces and newlines. Some, including `Markdown.pl` and
+`showdown`, convert an internal newline into a `<br />` tag.
+But this makes things difficult for those who like to hard-wrap
+their paragraphs, since a line break in the midst of a code
+span will cause an unintended line break in the output. Others
+just leave internal spaces as they are, which is fine if only
+HTML is being targeted.)
+
+.
+`foo `` bar`
+.
+<p><code>foo `` bar</code></p>
+.
+
+Note that backslash escapes do not work in code spans. All backslashes
+are treated literally:
+
+.
+`foo\`bar`
+.
+<p><code>foo\</code>bar`</p>
+.
+
+Backslash escapes are never needed, because one can always choose a
+string of *n* backtick characters as delimiters, where the code does
+not contain any strings of exactly *n* backtick characters.
+
+Code span backticks have higher precedence than any other inline
+constructs except HTML tags and autolinks. Thus, for example, this is
+not parsed as emphasized text, since the second `*` is part of a code
+span:
+
+.
+*foo`*`
+.
+<p>*foo<code>*</code></p>
+.
+
+And this is not parsed as a link:
+
+.
+[not a `link](/foo`)
+.
+<p>[not a <code>link](/foo</code>)</p>
+.
+
+But this is a link:
+
+.
+<http://foo.bar.`baz>`
+.
+<p><a href="http://foo.bar.%60baz">http://foo.bar.`baz</a>`</p>
+.
+
+And this is an HTML tag:
+
+.
+<a href="`">`
+.
+<p><a href="`">`</p>
+.
+
+When a backtick string is not closed by a matching backtick string,
+we just have literal backticks:
+
+.
+```foo``
+.
+<p>```foo``</p>
+.
+
+.
+`foo
+.
+<p>`foo</p>
+.
+
+## Emphasis and strong emphasis
+
+John Gruber's original [Markdown syntax
+description](http://daringfireball.net/projects/markdown/syntax#em) says:
+
+> Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+> emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML
+> `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML `<strong>`
+> tag.
+
+This is enough for most users, but these rules leave much undecided,
+especially when it comes to nested emphasis. The original
+`Markdown.pl` test suite makes it clear that triple `***` and
+`___` delimiters can be used for strong emphasis, and most
+implementations have also allowed the following patterns:
+
+``` markdown
+***strong emph***
+***strong** in emph*
+***emph* in strong**
+**in strong *emph***
+*in emph **strong***
+```
+
+The following patterns are less widely supported, but the intent
+is clear and they are useful (especially in contexts like bibliography
+entries):
+
+``` markdown
+*emph *with emph* in it*
+**strong **with strong** in it**
+```
+
+Many implementations have also restricted intraword emphasis to
+the `*` forms, to avoid unwanted emphasis in words containing
+internal underscores. (It is best practice to put these in code
+spans, but users often do not.)
+
+``` markdown
+internal emphasis: foo*bar*baz
+no emphasis: foo_bar_baz
+```
+
+The following rules capture all of these patterns, while allowing
+for efficient parsing strategies that do not backtrack:
+
+1. A single `*` character [can open emphasis](#can-open-emphasis)
+ <a id="can-open-emphasis"></a> iff
+
+ (a) it is not part of a sequence of four or more unescaped `*`s,
+ (b) it is not followed by whitespace, and
+ (c) either it is not followed by a `*` character or it is
+ followed immediately by strong emphasis.
+
+2. A single `_` character [can open emphasis](#can-open-emphasis) iff
+
+ (a) it is not part of a sequence of four or more unescaped `_`s,
+ (b) it is not followed by whitespace,
+ (c) it is not preceded by an ASCII alphanumeric character, and
+ (d) either it is not followed by a `_` character or it is
+ followed immediately by strong emphasis.
+
+3. A single `*` character [can close emphasis](#can-close-emphasis)
+ <a id="can-close-emphasis"></a> iff
+
+ (a) it is not part of a sequence of four or more unescaped `*`s, and
+ (b) it is not preceded by whitespace.
+
+4. A single `_` character [can close emphasis](#can-close-emphasis) iff
+
+ (a) it is not part of a sequence of four or more unescaped `_`s,
+ (b) it is not preceded by whitespace, and
+ (c) it is not followed by an ASCII alphanumeric character.
+
+5. A double `**` [can open strong emphasis](#can-open-strong-emphasis)
+ <a id="can-open-strong-emphasis" ></a> iff
+
+ (a) it is not part of a sequence of four or more unescaped `*`s,
+ (b) it is not followed by whitespace, and
+ (c) either it is not followed by a `*` character or it is
+ followed immediately by emphasis.
+
+6. A double `__` [can open strong emphasis](#can-open-strong-emphasis)
+ iff
+
+ (a) it is not part of a sequence of four or more unescaped `_`s,
+ (b) it is not followed by whitespace, and
+ (c) it is not preceded by an ASCII alphanumeric character, and
+ (d) either it is not followed by a `_` character or it is
+ followed immediately by emphasis.
+
+7. A double `**` [can close strong emphasis](#can-close-strong-emphasis)
+ <a id="can-close-strong-emphasis" ></a> iff
+
+ (a) it is not part of a sequence of four or more unescaped `*`s, and
+ (b) it is not preceded by whitespace.
+
+8. A double `__` [can close strong emphasis](#can-close-strong-emphasis)
+ iff
+
+ (a) it is not part of a sequence of four or more unescaped `_`s,
+ (b) it is not preceded by whitespace, and
+ (c) it is not followed by an ASCII alphanumeric character.
+
+9. Emphasis begins with a delimiter that [can open
+ emphasis](#can-open-emphasis) and includes inlines parsed
+ sequentially until a delimiter that [can close
+ emphasis](#can-close-emphasis), and that uses the same
+ character (`_` or `*`) as the opening delimiter, is reached.
+
+10. Strong emphasis begins with a delimiter that [can open strong
+ emphasis](#can-open-strong-emphasis) and includes inlines parsed
+ sequentially until a delimiter that [can close strong
+ emphasis](#can-close-strong-emphasis), and that uses the
+ same character (`_` or `*`) as the opening delimiter, is reached.
+
+These rules can be illustrated through a series of examples.
+
+Simple emphasis:
+
+.
+*foo bar*
+.
+<p><em>foo bar</em></p>
+.
+
+.
+_foo bar_
+.
+<p><em>foo bar</em></p>
+.
+
+Simple strong emphasis:
+
+.
+**foo bar**
+.
+<p><strong>foo bar</strong></p>
+.
+
+.
+__foo bar__
+.
+<p><strong>foo bar</strong></p>
+.
+
+Emphasis can continue over line breaks:
+
+.
+*foo
+bar*
+.
+<p><em>foo
+bar</em></p>
+.
+
+.
+_foo
+bar_
+.
+<p><em>foo
+bar</em></p>
+.
+
+.
+**foo
+bar**
+.
+<p><strong>foo
+bar</strong></p>
+.
+
+.
+__foo
+bar__
+.
+<p><strong>foo
+bar</strong></p>
+.
+
+Emphasis can contain other inline constructs:
+
+.
+*foo [bar](/url)*
+.
+<p><em>foo <a href="/url">bar</a></em></p>
+.
+
+.
+_foo [bar](/url)_
+.
+<p><em>foo <a href="/url">bar</a></em></p>
+.
+
+.
+**foo [bar](/url)**
+.
+<p><strong>foo <a href="/url">bar</a></strong></p>
+.
+
+.
+__foo [bar](/url)__
+.
+<p><strong>foo <a href="/url">bar</a></strong></p>
+.
+
+Symbols contained in other inline constructs will not
+close emphasis:
+
+.
+*foo [bar*](/url)
+.
+<p>*foo <a href="/url">bar*</a></p>
+.
+
+.
+_foo [bar_](/url)
+.
+<p>_foo <a href="/url">bar_</a></p>
+.
+
+.
+**<a href="**">
+.
+<p>**<a href="**"></p>
+.
+
+.
+__<a href="__">
+.
+<p>__<a href="__"></p>
+.
+
+.
+*a `*`*
+.
+<p><em>a <code>*</code></em></p>
+.
+
+.
+_a `_`_
+.
+<p><em>a <code>_</code></em></p>
+.
+
+.
+**a<http://foo.bar?q=**>
+.
+<p>**a<a href="http://foo.bar?q=**">http://foo.bar?q=**</a></p>
+.
+
+.
+__a<http://foo.bar?q=__>
+.
+<p>__a<a href="http://foo.bar?q=__">http://foo.bar?q=__</a></p>
+.
+
+This is not emphasis, because the opening delimiter is
+followed by white space:
+
+.
+and * foo bar*
+.
+<p>and * foo bar*</p>
+.
+
+.
+_ foo bar_
+.
+<p>_ foo bar_</p>
+.
+
+.
+and ** foo bar**
+.
+<p>and ** foo bar**</p>
+.
+
+.
+__ foo bar__
+.
+<p>__ foo bar__</p>
+.
+
+This is not emphasis, because the closing delimiter is
+preceded by white space:
+
+.
+and *foo bar *
+.
+<p>and *foo bar *</p>
+.
+
+.
+and _foo bar _
+.
+<p>and _foo bar _</p>
+.
+
+.
+and **foo bar **
+.
+<p>and **foo bar **</p>
+.
+
+.
+and __foo bar __
+.
+<p>and __foo bar __</p>
+.
+
+The rules imply that a sequence of four or more unescaped `*` or
+`_` characters will always be parsed as a literal string:
+
+.
+****hi****
+.
+<p>****hi****</p>
+.
+
+.
+_____hi_____
+.
+<p>_____hi_____</p>
+.
+
+.
+Sign here: _________
+.
+<p>Sign here: _________</p>
+.
+
+The rules also imply that there can be no empty emphasis or strong
+emphasis:
+
+.
+** is not an empty emphasis
+.
+<p>** is not an empty emphasis</p>
+.
+
+.
+**** is not an empty strong emphasis
+.
+<p>**** is not an empty strong emphasis</p>
+.
+
+To include `*` or `_` in emphasized sections, use backslash escapes
+or code spans:
+
+.
+*here is a \**
+.
+<p><em>here is a *</em></p>
+.
+
+.
+__this is a double underscore (`__`)__
+.
+<p><strong>this is a double underscore (<code>__</code>)</strong></p>
+.
+
+`*` delimiters allow intra-word emphasis; `_` delimiters do not:
+
+.
+foo*bar*baz
+.
+<p>foo<em>bar</em>baz</p>
+.
+
+.
+foo_bar_baz
+.
+<p>foo_bar_baz</p>
+.
+
+.
+foo__bar__baz
+.
+<p>foo__bar__baz</p>
+.
+
+.
+_foo_bar_baz_
+.
+<p><em>foo_bar_baz</em></p>
+.
+
+.
+11*15*32
+.
+<p>11<em>15</em>32</p>
+.
+
+.
+11_15_32
+.
+<p>11_15_32</p>
+.
+
+Internal underscores will be ignored in underscore-delimited
+emphasis:
+
+.
+_foo_bar_baz_
+.
+<p><em>foo_bar_baz</em></p>
+.
+
+.
+__foo__bar__baz__
+.
+<p><strong>foo__bar__baz</strong></p>
+.
+
+The rules are sufficient for the following nesting patterns:
+
+.
+***foo bar***
+.
+<p><strong><em>foo bar</em></strong></p>
+.
+
+.
+___foo bar___
+.
+<p><strong><em>foo bar</em></strong></p>
+.
+
+.
+***foo** bar*
+.
+<p><em><strong>foo</strong> bar</em></p>
+.
+
+.
+___foo__ bar_
+.
+<p><em><strong>foo</strong> bar</em></p>
+.
+
+.
+***foo* bar**
+.
+<p><strong><em>foo</em> bar</strong></p>
+.
+
+.
+___foo_ bar__
+.
+<p><strong><em>foo</em> bar</strong></p>
+.
+
+.
+*foo **bar***
+.
+<p><em>foo <strong>bar</strong></em></p>
+.
+
+.
+_foo __bar___
+.
+<p><em>foo <strong>bar</strong></em></p>
+.
+
+.
+**foo *bar***
+.
+<p><strong>foo <em>bar</em></strong></p>
+.
+
+.
+__foo _bar___
+.
+<p><strong>foo <em>bar</em></strong></p>
+.
+
+.
+*foo **bar***
+.
+<p><em>foo <strong>bar</strong></em></p>
+.
+
+.
+_foo __bar___
+.
+<p><em>foo <strong>bar</strong></em></p>
+.
+
+.
+*foo *bar* baz*
+.
+<p><em>foo <em>bar</em> baz</em></p>
+.
+
+.
+_foo _bar_ baz_
+.
+<p><em>foo <em>bar</em> baz</em></p>
+.
+
+.
+**foo **bar** baz**
+.
+<p><strong>foo <strong>bar</strong> baz</strong></p>
+.
+
+.
+__foo __bar__ baz__
+.
+<p><strong>foo <strong>bar</strong> baz</strong></p>
+.
+
+.
+*foo **bar** baz*
+.
+<p><em>foo <strong>bar</strong> baz</em></p>
+.
+
+.
+_foo __bar__ baz_
+.
+<p><em>foo <strong>bar</strong> baz</em></p>
+.
+
+.
+**foo *bar* baz**
+.
+<p><strong>foo <em>bar</em> baz</strong></p>
+.
+
+.
+__foo _bar_ baz__
+.
+<p><strong>foo <em>bar</em> baz</strong></p>
+.
+
+Note that you cannot nest emphasis directly inside emphasis
+using the same delimeter, or strong emphasis directly inside
+strong emphasis:
+
+.
+**foo**
+.
+<p><strong>foo</strong></p>
+.
+
+.
+****foo****
+.
+<p>****foo****</p>
+.
+
+For these nestings, you need to switch delimiters:
+
+.
+*_foo_*
+.
+<p><em><em>foo</em></em></p>
+.
+
+.
+**__foo__**
+.
+<p><strong><strong>foo</strong></strong></p>
+.
+
+Note that a `*` followed by a `*` can close emphasis, and
+a `**` followed by a `*` can close strong emphasis (and
+similarly for `_` and `__`):
+
+.
+*foo**
+.
+<p><em>foo</em>*</p>
+.
+
+.
+*foo *bar**
+.
+<p><em>foo <em>bar</em></em></p>
+.
+
+.
+**foo***
+.
+<p><strong>foo</strong>*</p>
+.
+
+.
+***foo* bar***
+.
+<p><strong><em>foo</em> bar</strong>*</p>
+.
+
+.
+***foo** bar***
+.
+<p><em><strong>foo</strong> bar</em>**</p>
+.
+
+The following contains no strong emphasis, because the opening
+delimiter is closed by the first `*` before `bar`:
+
+.
+*foo**bar***
+.
+<p><em>foo</em><em>bar</em>**</p>
+.
+
+However, a string of four or more `****` can never close emphasis:
+
+.
+*foo****
+.
+<p>*foo****</p>
+.
+
+Note that there are some asymmetries here:
+
+.
+*foo**
+
+**foo*
+.
+<p><em>foo</em>*</p>
+<p>**foo*</p>
+.
+
+.
+*foo *bar**
+
+**foo* bar*
+.
+<p><em>foo <em>bar</em></em></p>
+<p>**foo* bar*</p>
+.
+
+More cases with mismatched delimiters:
+
+.
+**foo* bar*
+.
+<p>**foo* bar*</p>
+.
+
+.
+*bar***
+.
+<p><em>bar</em>**</p>
+.
+
+.
+***foo*
+.
+<p>***foo*</p>
+.
+
+.
+**bar***
+.
+<p><strong>bar</strong>*</p>
+.
+
+.
+***foo**
+.
+<p>***foo**</p>
+.
+
+.
+***foo *bar*
+.
+<p>***foo <em>bar</em></p>
+.
+
+## Links
+
+A link contains a [link label](#link-label) (the visible text),
+a [destination](#destination) (the URI that is the link destination),
+and optionally a [link title](#link-title). There are two basic kinds
+of links in Markdown. In [inline links](#inline-links) the destination
+and title are given immediately after the label. In [reference
+links](#reference-links) the destination and title are defined elsewhere
+in the document.
+
+A [link label](#link-label) <a id="link-label"></a> consists of
+
+- an opening `[`, followed by
+- zero or more backtick code spans, autolinks, HTML tags, link labels,
+ backslash-escaped ASCII punctuation characters, or non-`]` characters,
+ followed by
+- a closing `]`.
+
+These rules are motivated by the following intuitive ideas:
+
+- A link label is a container for inline elements.
+- The square brackets bind more tightly than emphasis markers,
+ but less tightly than `<>` or `` ` ``.
+- Link labels may contain material in matching square brackets.
+
+A [link destination](#link-destination) <a id="link-destination"></a>
+consists of either
+
+- a sequence of zero or more characters between an opening `<` and a
+ closing `>` that contains no line breaks or unescaped `<` or `>`
+ characters, or
+
+- a nonempty sequence of characters that does not include
+ ASCII space or control characters, and includes parentheses
+ only if (a) they are backslash-escaped or (b) they are part of
+ a balanced pair of unescaped parentheses that is not itself
+ inside a balanced pair of unescaped paretheses.
+
+A [link title](#link-title) <a id="link-title"></a> consists of either
+
+- a sequence of zero or more characters between straight double-quote
+ characters (`"`), including a `"` character only if it is
+ backslash-escaped, or
+
+- a sequence of zero or more characters between straight single-quote
+ characters (`'`), including a `'` character only if it is
+ backslash-escaped, or
+
+- a sequence of zero or more characters between matching parentheses
+ (`(...)`), including a `)` character only if it is backslash-escaped.
+
+An [inline link](#inline-link) <a id="inline-link"></a>
+consists of a [link label](#link-label) followed immediately
+by a left parenthesis `(`, optional whitespace,
+an optional [link destination](#link-destination),
+an optional [link title](#link-title) separated from the link
+destination by whitespace, optional whitespace, and a right
+parenthesis `)`. The link's text consists of the label (excluding
+the enclosing square brackets) parsed as inlines. The link's
+URI consists of the link destination, excluding enclosing `<...>` if
+present, with backslash-escapes in effect as described above. The
+link's title consists of the link title, excluding its enclosing
+delimiters, with backslash-escapes in effect as described above.
+
+Here is a simple inline link:
+
+.
+[link](/uri "title")
+.
+<p><a href="/uri" title="title">link</a></p>
+.
+
+The title may be omitted:
+
+.
+[link](/uri)
+.
+<p><a href="/uri">link</a></p>
+.
+
+Both the title and the destination may be omitted:
+
+.
+[link]()
+.
+<p><a href="">link</a></p>
+.
+
+.
+[link](<>)
+.
+<p><a href="">link</a></p>
+.
+
+
+If the destination contains spaces, it must be enclosed in pointy
+braces:
+
+.
+[link](/my uri)
+.
+<p>[link](/my uri)</p>
+.
+
+.
+[link](</my uri>)
+.
+<p><a href="/my%20uri">link</a></p>
+.
+
+The destination cannot contain line breaks, even with pointy braces:
+
+.
+[link](foo
+bar)
+.
+<p>[link](foo
+bar)</p>
+.
+
+One level of balanced parentheses is allowed without escaping:
+
+.
+[link]((foo)and(bar))
+.
+<p><a href="(foo)and(bar)">link</a></p>
+.
+
+However, if you have parentheses within parentheses, you need to escape
+or use the `<...>` form:
+
+.
+[link](foo(and(bar)))
+.
+<p>[link](foo(and(bar)))</p>
+.
+
+.
+[link](foo(and\(bar\)))
+.
+<p><a href="foo(and(bar))">link</a></p>
+.
+
+.
+[link](<foo(and(bar))>)
+.
+<p><a href="foo(and(bar))">link</a></p>
+.
+
+Parentheses and other symbols can also be escaped, as usual
+in Markdown:
+
+.
+[link](foo\)\:)
+.
+<p><a href="foo):">link</a></p>
+.
+
+URL-escaping and should be left alone inside the destination, as all URL-escaped characters
+are also valid URL characters. HTML entities in the destination will be parsed into their UTF8
+codepoints, as usual, and optionally URL-escaped when written as HTML.
+
+.
+[link](foo%20b&auml;)
+.
+<p><a href="foo%20b%C3%A4">link</a></p>
+.
+
+Note that, because titles can often be parsed as destinations,
+if you try to omit the destination and keep the title, you'll
+get unexpected results:
+
+.
+[link]("title")
+.
+<p><a href="%22title%22">link</a></p>
+.
+
+Titles may be in single quotes, double quotes, or parentheses:
+
+.
+[link](/url "title")
+[link](/url 'title')
+[link](/url (title))
+.
+<p><a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a></p>
+.
+
+Backslash escapes and entities may be used in titles:
+
+.
+[link](/url "title \"&quot;")
+.
+<p><a href="/url" title="title &quot;&quot;">link</a></p>
+.
+
+Nested balanced quotes are not allowed without escaping:
+
+.
+[link](/url "title "and" title")
+.
+<p>[link](/url &quot;title &quot;and&quot; title&quot;)</p>
+.
+
+But it is easy to work around this by using a different quote type:
+
+.
+[link](/url 'title "and" title')
+.
+<p><a href="/url" title="title &quot;and&quot; title">link</a></p>
+.
+
+(Note: `Markdown.pl` did allow double quotes inside a double-quoted
+title, and its test suite included a test demonstrating this.
+But it is hard to see a good rationale for the extra complexity this
+brings, since there are already many ways---backslash escaping,
+entities, or using a different quote type for the enclosing title---to
+write titles containing double quotes. `Markdown.pl`'s handling of
+titles has a number of other strange features. For example, it allows
+single-quoted titles in inline links, but not reference links. And, in
+reference links but not inline links, it allows a title to begin with
+`"` and end with `)`. `Markdown.pl` 1.0.1 even allows titles with no closing
+quotation mark, though 1.0.2b8 does not. It seems preferable to adopt
+a simple, rational rule that works the same way in inline links and
+link reference definitions.)
+
+Whitespace is allowed around the destination and title:
+
+.
+[link]( /uri
+ "title" )
+.
+<p><a href="/uri" title="title">link</a></p>
+.
+
+But it is not allowed between the link label and the
+following parenthesis:
+
+.
+[link] (/uri)
+.
+<p>[link] (/uri)</p>
+.
+
+Note that this is not a link, because the closing `]` occurs in
+an HTML tag:
+
+.
+[foo <bar attr="](baz)">
+.
+<p>[foo <bar attr="](baz)"></p>
+.
+
+
+There are three kinds of [reference links](#reference-link):
+<a id="reference-link"></a>
+
+A [full reference link](#full-reference-link) <a id="full-reference-link"></a>
+consists of a [link label](#link-label), optional whitespace, and
+another [link label](#link-label) that [matches](#matches) a
+[link reference definition](#link-reference-definition) elsewhere in the
+document.
+
+One label [matches](#matches) <a id="matches"></a>
+another just in case their normalized forms are equal. To normalize a
+label, perform the *unicode case fold* and collapse consecutive internal
+whitespace to a single space. If there are multiple matching reference
+link definitions, the one that comes first in the document is used. (It
+is desirable in such cases to emit a warning.)
+
+The contents of the first link label are parsed as inlines, which are
+used as the link's text. The link's URI and title are provided by the
+matching [link reference definition](#link-reference-definition).
+
+Here is a simple example:
+
+.
+[foo][bar]
+
+[bar]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+The first label can contain inline content:
+
+.
+[*foo\!*][bar]
+
+[bar]: /url "title"
+.
+<p><a href="/url" title="title"><em>foo!</em></a></p>
+.
+
+Matching is case-insensitive:
+
+.
+[foo][BaR]
+
+[bar]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+Unicode case fold is used:
+
+.
+[Толпой][Толпой] is a Russian word.
+
+[ТОЛПОЙ]: /url
+.
+<p><a href="/url">Толпой</a> is a Russian word.</p>
+.
+
+Consecutive internal whitespace is treated as one space for
+purposes of determining matching:
+
+.
+[Foo
+ bar]: /url
+
+[Baz][Foo bar]
+.
+<p><a href="/url">Baz</a></p>
+.
+
+There can be whitespace between the two labels:
+
+.
+[foo] [bar]
+
+[bar]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+.
+[foo]
+[bar]
+
+[bar]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+When there are multiple matching [link reference
+definitions](#link-reference-definition), the first is used:
+
+.
+[foo]: /url1
+
+[foo]: /url2
+
+[bar][foo]
+.
+<p><a href="/url1">bar</a></p>
+.
+
+Note that matching is performed on normalized strings, not parsed
+inline content. So the following does not match, even though the
+labels define equivalent inline content:
+
+.
+[bar][foo\!]
+
+[foo!]: /url
+.
+<p>[bar][foo!]</p>
+.
+
+A [collapsed reference link](#collapsed-reference-link)
+<a id="collapsed-reference-link"></a> consists of a [link
+label](#link-label) that [matches](#matches) a [link reference
+definition](#link-reference-definition) elsewhere in the
+document, optional whitespace, and the string `[]`. The contents of the
+first link label are parsed as inlines, which are used as the link's
+text. The link's URI and title are provided by the matching reference
+link definition. Thus, `[foo][]` is equivalent to `[foo][foo]`.
+
+.
+[foo][]
+
+[foo]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+.
+[*foo* bar][]
+
+[*foo* bar]: /url "title"
+.
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+.
+
+The link labels are case-insensitive:
+
+.
+[Foo][]
+
+[foo]: /url "title"
+.
+<p><a href="/url" title="title">Foo</a></p>
+.
+
+
+As with full reference links, whitespace is allowed
+between the two sets of brackets:
+
+.
+[foo]
+[]
+
+[foo]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+A [shortcut reference link](#shortcut-reference-link)
+<a id="shortcut-reference-link"></a> consists of a [link
+label](#link-label) that [matches](#matches) a [link reference
+definition](#link-reference-definition) elsewhere in the
+document and is not followed by `[]` or a link label.
+The contents of the first link label are parsed as inlines,
+which are used as the link's text. the link's URI and title
+are provided by the matching link reference definition.
+Thus, `[foo]` is equivalent to `[foo][]`.
+
+.
+[foo]
+
+[foo]: /url "title"
+.
+<p><a href="/url" title="title">foo</a></p>
+.
+
+.
+[*foo* bar]
+
+[*foo* bar]: /url "title"
+.
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+.
+
+.
+[[*foo* bar]]
+
+[*foo* bar]: /url "title"
+.
+<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
+.
+
+The link labels are case-insensitive:
+
+.
+[Foo]
+
+[foo]: /url "title"
+.
+<p><a href="/url" title="title">Foo</a></p>
+.
+
+If you just want bracketed text, you can backslash-escape the
+opening bracket to avoid links:
+
+.
+\[foo]
+
+[foo]: /url "title"
+.
+<p>[foo]</p>
+.
+
+Note that this is a link, because link labels bind more tightly
+than emphasis:
+
+.
+[foo*]: /url
+
+*[foo*]
+.
+<p>*<a href="/url">foo*</a></p>
+.
+
+However, this is not, because link labels bind less
+tightly than code backticks:
+
+.
+[foo`]: /url
+
+[foo`]`
+.
+<p>[foo<code>]</code></p>
+.
+
+Link labels can contain matched square brackets:
+
+.
+[[[foo]]]
+
+[[[foo]]]: /url
+.
+<p><a href="/url">[[foo]]</a></p>
+.
+
+.
+[[[foo]]]
+
+[[[foo]]]: /url1
+[foo]: /url2
+.
+<p><a href="/url1">[[foo]]</a></p>
+.
+
+For non-matching brackets, use backslash escapes:
+
+.
+[\[foo]
+
+[\[foo]: /url
+.
+<p><a href="/url">[foo</a></p>
+.
+
+Full references take precedence over shortcut references:
+
+.
+[foo][bar]
+
+[foo]: /url1
+[bar]: /url2
+.
+<p><a href="/url2">foo</a></p>
+.
+
+In the following case `[bar][baz]` is parsed as a reference,
+`[foo]` as normal text:
+
+.
+[foo][bar][baz]
+
+[baz]: /url
+.
+<p>[foo]<a href="/url">bar</a></p>
+.
+
+Here, though, `[foo][bar]` is parsed as a reference, since
+`[bar]` is defined:
+
+.
+[foo][bar][baz]
+
+[baz]: /url1
+[bar]: /url2
+.
+<p><a href="/url2">foo</a><a href="/url1">baz</a></p>
+.
+
+Here `[foo]` is not parsed as a shortcut reference, because it
+is followed by a link label (even though `[bar]` is not defined):
+
+.
+[foo][bar][baz]
+
+[baz]: /url1
+[foo]: /url2
+.
+<p>[foo]<a href="/url1">bar</a></p>
+.
+
+
+## Images
+
+An (unescaped) exclamation mark (`!`) followed by a reference or
+inline link will be parsed as an image. The link label will be
+used as the image's alt text, and the link title, if any, will
+be used as the image's title.
+
+.
+![foo](/url "title")
+.
+<p><img src="/url" alt="foo" title="title" /></p>
+.
+
+.
+![foo *bar*]
+
+[foo *bar*]: train.jpg "train & tracks"
+.
+<p><img src="train.jpg" alt="foo &lt;em&gt;bar&lt;/em&gt;" title="train &amp; tracks" /></p>
+.
+
+.
+![foo *bar*][]
+
+[foo *bar*]: train.jpg "train & tracks"
+.
+<p><img src="train.jpg" alt="foo &lt;em&gt;bar&lt;/em&gt;" title="train &amp; tracks" /></p>
+.
+
+.
+![foo *bar*][foobar]
+
+[FOOBAR]: train.jpg "train & tracks"
+.
+<p><img src="train.jpg" alt="foo &lt;em&gt;bar&lt;/em&gt;" title="train &amp; tracks" /></p>
+.
+
+.
+![foo](train.jpg)
+.
+<p><img src="train.jpg" alt="foo" /></p>
+.
+
+.
+My ![foo bar](/path/to/train.jpg "title" )
+.
+<p>My <img src="/path/to/train.jpg" alt="foo bar" title="title" /></p>
+.
+
+.
+![foo](<url>)
+.
+<p><img src="url" alt="foo" /></p>
+.
+
+.
+![](/url)
+.
+<p><img src="/url" alt="" /></p>
+.
+
+Reference-style:
+
+.
+![foo] [bar]
+
+[bar]: /url
+.
+<p><img src="/url" alt="foo" /></p>
+.
+
+.
+![foo] [bar]
+
+[BAR]: /url
+.
+<p><img src="/url" alt="foo" /></p>
+.
+
+Collapsed:
+
+.
+![foo][]
+
+[foo]: /url "title"
+.
+<p><img src="/url" alt="foo" title="title" /></p>
+.
+
+.
+![*foo* bar][]
+
+[*foo* bar]: /url "title"
+.
+<p><img src="/url" alt="&lt;em&gt;foo&lt;/em&gt; bar" title="title" /></p>
+.
+
+The labels are case-insensitive:
+
+.
+![Foo][]
+
+[foo]: /url "title"
+.
+<p><img src="/url" alt="Foo" title="title" /></p>
+.
+
+As with full reference links, whitespace is allowed
+between the two sets of brackets:
+
+.
+![foo]
+[]
+
+[foo]: /url "title"
+.
+<p><img src="/url" alt="foo" title="title" /></p>
+.
+
+Shortcut:
+
+.
+![foo]
+
+[foo]: /url "title"
+.
+<p><img src="/url" alt="foo" title="title" /></p>
+.
+
+.
+![*foo* bar]
+
+[*foo* bar]: /url "title"
+.
+<p><img src="/url" alt="&lt;em&gt;foo&lt;/em&gt; bar" title="title" /></p>
+.
+
+.
+![[foo]]
+
+[[foo]]: /url "title"
+.
+<p><img src="/url" alt="[foo]" title="title" /></p>
+.
+
+The link labels are case-insensitive:
+
+.
+![Foo]
+
+[foo]: /url "title"
+.
+<p><img src="/url" alt="Foo" title="title" /></p>
+.
+
+If you just want bracketed text, you can backslash-escape the
+opening `!` and `[`:
+
+.
+\!\[foo]
+
+[foo]: /url "title"
+.
+<p>![foo]</p>
+.
+
+If you want a link after a literal `!`, backslash-escape the
+`!`:
+
+.
+\![foo]
+
+[foo]: /url "title"
+.
+<p>!<a href="/url" title="title">foo</a></p>
+.
+
+## Autolinks
+
+Autolinks are absolute URIs and email addresses inside `<` and `>`.
+They are parsed as links, with the URL or email address as the link
+label.
+
+A [URI autolink](#uri-autolink) <a id="uri-autolink"></a>
+consists of `<`, followed by an [absolute
+URI](#absolute-uri) not containing `<`, followed by `>`. It is parsed
+as a link to the URI, with the URI as the link's label.
+
+An [absolute URI](#absolute-uri), <a id="absolute-uri"></a>
+for these purposes, consists of a [scheme](#scheme) followed by a colon (`:`)
+followed by zero or more characters other than ASCII whitespace and
+control characters, `<`, and `>`. If the URI includes these characters,
+you must use percent-encoding (e.g. `%20` for a space).
+
+The following [schemes](#scheme) <a id="scheme"></a>
+are recognized (case-insensitive):
+`coap`, `doi`, `javascript`, `aaa`, `aaas`, `about`, `acap`, `cap`,
+`cid`, `crid`, `data`, `dav`, `dict`, `dns`, `file`, `ftp`, `geo`, `go`,
+`gopher`, `h323`, `http`, `https`, `iax`, `icap`, `im`, `imap`, `info`,
+`ipp`, `iris`, `iris.beep`, `iris.xpc`, `iris.xpcs`, `iris.lwz`, `ldap`,
+`mailto`, `mid`, `msrp`, `msrps`, `mtqp`, `mupdate`, `news`, `nfs`,
+`ni`, `nih`, `nntp`, `opaquelocktoken`, `pop`, `pres`, `rtsp`,
+`service`, `session`, `shttp`, `sieve`, `sip`, `sips`, `sms`, `snmp`,`
+soap.beep`, `soap.beeps`, `tag`, `tel`, `telnet`, `tftp`, `thismessage`,
+`tn3270`, `tip`, `tv`, `urn`, `vemmi`, `ws`, `wss`, `xcon`,
+`xcon-userid`, `xmlrpc.beep`, `xmlrpc.beeps`, `xmpp`, `z39.50r`,
+`z39.50s`, `adiumxtra`, `afp`, `afs`, `aim`, `apt`,` attachment`, `aw`,
+`beshare`, `bitcoin`, `bolo`, `callto`, `chrome`,` chrome-extension`,
+`com-eventbrite-attendee`, `content`, `cvs`,` dlna-playsingle`,
+`dlna-playcontainer`, `dtn`, `dvb`, `ed2k`, `facetime`, `feed`,
+`finger`, `fish`, `gg`, `git`, `gizmoproject`, `gtalk`, `hcp`, `icon`,
+`ipn`, `irc`, `irc6`, `ircs`, `itms`, `jar`, `jms`, `keyparc`, `lastfm`,
+`ldaps`, `magnet`, `maps`, `market`,` message`, `mms`, `ms-help`,
+`msnim`, `mumble`, `mvn`, `notes`, `oid`, `palm`, `paparazzi`,
+`platform`, `proxy`, `psyc`, `query`, `res`, `resource`, `rmi`, `rsync`,
+`rtmp`, `secondlife`, `sftp`, `sgn`, `skype`, `smb`, `soldat`,
+`spotify`, `ssh`, `steam`, `svn`, `teamspeak`, `things`, `udp`,
+`unreal`, `ut2004`, `ventrilo`, `view-source`, `webcal`, `wtai`,
+`wyciwyg`, `xfire`, `xri`, `ymsgr`.
+
+Here are some valid autolinks:
+
+.
+<http://foo.bar.baz>
+.
+<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p>
+.
+
+.
+<http://foo.bar.baz?q=hello&id=22&boolean>
+.
+<p><a href="http://foo.bar.baz?q=hello&amp;id=22&amp;boolean">http://foo.bar.baz?q=hello&amp;id=22&amp;boolean</a></p>
+.
+
+.
+<irc://foo.bar:2233/baz>
+.
+<p><a href="irc://foo.bar:2233/baz">irc://foo.bar:2233/baz</a></p>
+.
+
+Uppercase is also fine:
+
+.
+<MAILTO:FOO@BAR.BAZ>
+.
+<p><a href="MAILTO:FOO@BAR.BAZ">MAILTO:FOO@BAR.BAZ</a></p>
+.
+
+Spaces are not allowed in autolinks:
+
+.
+<http://foo.bar/baz bim>
+.
+<p>&lt;http://foo.bar/baz bim&gt;</p>
+.
+
+An [email autolink](#email-autolink) <a id="email-autolink"></a>
+consists of `<`, followed by an [email address](#email-address),
+followed by `>`. The link's label is the email address,
+and the URL is `mailto:` followed by the email address.
+
+An [email address](#email-address), <a id="email-address"></a>
+for these purposes, is anything that matches
+the [non-normative regex from the HTML5
+spec](http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-%28type=email%29):
+
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
+ (?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
+
+Examples of email autolinks:
+
+.
+<foo@bar.baz.com>
+.
+<p><a href="mailto:foo@bar.baz.com">foo@bar.baz.com</a></p>
+.
+
+.
+<foo+special@Bar.baz-bar0.com>
+.
+<p><a href="mailto:foo+special@Bar.baz-bar0.com">foo+special@Bar.baz-bar0.com</a></p>
+.
+
+These are not autolinks:
+
+.
+<>
+.
+<p>&lt;&gt;</p>
+.
+
+.
+<heck://bing.bong>
+.
+<p>&lt;heck://bing.bong&gt;</p>
+.
+
+.
+< http://foo.bar >
+.
+<p>&lt; http://foo.bar &gt;</p>
+.
+
+.
+<foo.bar.baz>
+.
+<p>&lt;foo.bar.baz&gt;</p>
+.
+
+.
+<localhost:5001/foo>
+.
+<p>&lt;localhost:5001/foo&gt;</p>
+.
+
+.
+http://google.com
+.
+<p>http://google.com</p>
+.
+
+.
+foo@bar.baz.com
+.
+<p>foo@bar.baz.com</p>
+.
+
+## Raw HTML
+
+Text between `<` and `>` that looks like an HTML tag is parsed as a
+raw HTML tag and will be rendered in HTML without escaping.
+Tag and attribute names are not limited to current HTML tags,
+so custom tags (and even, say, DocBook tags) may be used.
+
+Here is the grammar for tags:
+
+A [tag name](#tag-name) <a id="tag-name"></a> consists of an ASCII letter
+followed by zero or more ASCII letters or digits.
+
+An [attribute](#attribute) <a id="attribute"></a> consists of whitespace,
+an **attribute name**, and an optional **attribute value
+specification**.
+
+An [attribute name](#attribute-name) <a id="attribute-name"></a>
+consists of an ASCII letter, `_`, or `:`, followed by zero or more ASCII
+letters, digits, `_`, `.`, `:`, or `-`. (Note: This is the XML
+specification restricted to ASCII. HTML5 is laxer.)
+
+An [attribute value specification](#attribute-value-specification)
+<a id="attribute-value-specification"></a> consists of optional whitespace,
+a `=` character, optional whitespace, and an [attribute
+value](#attribute-value).
+
+An [attribute value](#attribute-value) <a id="attribute-value"></a>
+consists of an [unquoted attribute value](#unquoted-attribute-value),
+a [single-quoted attribute value](#single-quoted-attribute-value),
+or a [double-quoted attribute value](#double-quoted-attribute-value).
+
+An [unquoted attribute value](#unquoted-attribute-value)
+<a id="unquoted-attribute-value"></a> is a nonempty string of characters not
+including spaces, `"`, `'`, `=`, `<`, `>`, or `` ` ``.
+
+A [single-quoted attribute value](#single-quoted-attribute-value)
+<a id="single-quoted-attribute-value"></a> consists of `'`, zero or more
+characters not including `'`, and a final `'`.
+
+A [double-quoted attribute value](#double-quoted-attribute-value)
+<a id="double-quoted-attribute-value"></a> consists of `"`, zero or more
+characters not including `"`, and a final `"`.
+
+An [open tag](#open-tag) <a id="open-tag"></a> consists of a `<` character,
+a [tag name](#tag-name), zero or more [attributes](#attribute),
+optional whitespace, an optional `/` character, and a `>` character.
+
+A [closing tag](#closing-tag) <a id="closing-tag"></a> consists of the
+string `</`, a [tag name](#tag-name), optional whitespace, and the
+character `>`.
+
+An [HTML comment](#html-comment) <a id="html-comment"></a> consists of the
+string `<!--`, a string of characters not including the string `--`, and
+the string `-->`.
+
+A [processing instruction](#processing-instruction)
+<a id="processing-instruction"></a> consists of the string `<?`, a string
+of characters not including the string `?>`, and the string
+`?>`.
+
+A [declaration](#declaration) <a id="declaration"></a> consists of the
+string `<!`, a name consisting of one or more uppercase ASCII letters,
+whitespace, a string of characters not including the character `>`, and
+the character `>`.
+
+A [CDATA section](#cdata-section) <a id="cdata-section"></a> consists of
+the string `<![CDATA[`, a string of characters not including the string
+`]]>`, and the string `]]>`.
+
+An [HTML tag](#html-tag) <a id="html-tag"></a> consists of an [open
+tag](#open-tag), a [closing tag](#closing-tag), an [HTML
+comment](#html-comment), a [processing
+instruction](#processing-instruction), an [element type
+declaration](#element-type-declaration), or a [CDATA
+section](#cdata-section).
+
+Here are some simple open tags:
+
+.
+<a><bab><c2c>
+.
+<p><a><bab><c2c></p>
+.
+
+Empty elements:
+
+.
+<a/><b2/>
+.
+<p><a/><b2/></p>
+.
+
+Whitespace is allowed:
+
+.
+<a /><b2
+data="foo" >
+.
+<p><a /><b2
+data="foo" ></p>
+.
+
+With attributes:
+
+.
+<a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 />
+.
+<p><a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 /></p>
+.
+
+Illegal tag names, not parsed as HTML:
+
+.
+<33> <__>
+.
+<p>&lt;33&gt; &lt;__&gt;</p>
+.
+
+Illegal attribute names:
+
+.
+<a h*#ref="hi">
+.
+<p>&lt;a h*#ref=&quot;hi&quot;&gt;</p>
+.
+
+Illegal attribute values:
+
+.
+<a href="hi'> <a href=hi'>
+.
+<p>&lt;a href=&quot;hi'&gt; &lt;a href=hi'&gt;</p>
+.
+
+Illegal whitespace:
+
+.
+< a><
+foo><bar/ >
+.
+<p>&lt; a&gt;&lt;
+foo&gt;&lt;bar/ &gt;</p>
+.
+
+Missing whitespace:
+
+.
+<a href='bar'title=title>
+.
+<p>&lt;a href='bar'title=title&gt;</p>
+.
+
+Closing tags:
+
+.
+</a>
+</foo >
+.
+<p></a>
+</foo ></p>
+.
+
+Illegal attributes in closing tag:
+
+.
+</a href="foo">
+.
+<p>&lt;/a href=&quot;foo&quot;&gt;</p>
+.
+
+Comments:
+
+.
+foo <!-- this is a
+comment - with hyphen -->
+.
+<p>foo <!-- this is a
+comment - with hyphen --></p>
+.
+
+.
+foo <!-- not a comment -- two hyphens -->
+.
+<p>foo &lt;!-- not a comment -- two hyphens --&gt;</p>
+.
+
+Processing instructions:
+
+.
+foo <?php echo $a; ?>
+.
+<p>foo <?php echo $a; ?></p>
+.
+
+Declarations:
+
+.
+foo <!ELEMENT br EMPTY>
+.
+<p>foo <!ELEMENT br EMPTY></p>
+.
+
+CDATA sections:
+
+.
+foo <![CDATA[>&<]]>
+.
+<p>foo <![CDATA[>&<]]></p>
+.
+
+Entities are preserved in HTML attributes:
+
+.
+<a href="&ouml;">
+.
+<p><a href="&ouml;"></p>
+.
+
+Backslash escapes do not work in HTML attributes:
+
+.
+<a href="\*">
+.
+<p><a href="\*"></p>
+.
+
+.
+<a href="\"">
+.
+<p>&lt;a href=&quot;&quot;&quot;&gt;</p>
+.
+
+## Hard line breaks
+
+A line break (not in a code span or HTML tag) that is preceded
+by two or more spaces is parsed as a linebreak (rendered
+in HTML as a `<br />` tag):
+
+.
+foo
+baz
+.
+<p>foo<br />
+baz</p>
+.
+
+For a more visible alternative, a backslash before the newline may be
+used instead of two spaces:
+
+.
+foo\
+baz
+.
+<p>foo<br />
+baz</p>
+.
+
+More than two spaces can be used:
+
+.
+foo
+baz
+.
+<p>foo<br />
+baz</p>
+.
+
+Leading spaces at the beginning of the next line are ignored:
+
+.
+foo
+ bar
+.
+<p>foo<br />
+bar</p>
+.
+
+.
+foo\
+ bar
+.
+<p>foo<br />
+bar</p>
+.
+
+Line breaks can occur inside emphasis, links, and other constructs
+that allow inline content:
+
+.
+*foo
+bar*
+.
+<p><em>foo<br />
+bar</em></p>
+.
+
+.
+*foo\
+bar*
+.
+<p><em>foo<br />
+bar</em></p>
+.
+
+Line breaks do not occur inside code spans
+
+.
+`code
+span`
+.
+<p><code>code span</code></p>
+.
+
+.
+`code\
+span`
+.
+<p><code>code\ span</code></p>
+.
+
+or HTML tags:
+
+.
+<a href="foo
+bar">
+.
+<p><a href="foo
+bar"></p>
+.
+
+.
+<a href="foo\
+bar">
+.
+<p><a href="foo\
+bar"></p>
+.
+
+## Soft line breaks
+
+A regular line break (not in a code span or HTML tag) that is not
+preceded by two or more spaces is parsed as a softbreak. (A
+softbreak may be rendered in HTML either as a newline or as a space.
+The result will be the same in browsers. In the examples here, a
+newline will be used.)
+
+.
+foo
+baz
+.
+<p>foo
+baz</p>
+.
+
+Spaces at the end of the line and beginning of the next line are
+removed:
+
+.
+foo
+ baz
+.
+<p>foo
+baz</p>
+.
+
+A conforming parser may render a soft line break in HTML either as a
+line break or as a space.
+
+A renderer may also provide an option to render soft line breaks
+as hard line breaks.
+
+## Strings
+
+Any characters not given an interpretation by the above rules will
+be parsed as string content.
+
+.
+hello $.;'there
+.
+<p>hello $.;'there</p>
+.
+
+.
+Foo χρῆν
+.
+<p>Foo χρῆν</p>
+.
+
+Internal spaces are preserved verbatim:
+
+.
+Multiple spaces
+.
+<p>Multiple spaces</p>
+.
+
+<!-- END TESTS -->
+
+# Appendix A: A parsing strategy {-}
+
+## Overview {-}
+
+Parsing has two phases:
+
+1. In the first phase, lines of input are consumed and the block
+structure of the document---its division into paragraphs, block quotes,
+list items, and so on---is constructed. Text is assigned to these
+blocks but not parsed. Link reference definitions are parsed and a
+map of links is constructed.
+
+2. In the second phase, the raw text contents of paragraphs and headers
+are parsed into sequences of Markdown inline elements (strings,
+code spans, links, emphasis, and so on), using the map of link
+references constructed in phase 1.
+
+## The document tree {-}
+
+At each point in processing, the document is represented as a tree of
+**blocks**. The root of the tree is a `document` block. The `document`
+may have any number of other blocks as **children**. These children
+may, in turn, have other blocks as children. The last child of a block
+is normally considered **open**, meaning that subsequent lines of input
+can alter its contents. (Blocks that are not open are **closed**.)
+Here, for example, is a possible document tree, with the open blocks
+marked by arrows:
+
+``` tree
+-> document
+ -> block_quote
+ paragraph
+ "Lorem ipsum dolor\nsit amet."
+ -> list (type=bullet tight=true bullet_char=-)
+ list_item
+ paragraph
+ "Qui *quodsi iracundia*"
+ -> list_item
+ -> paragraph
+ "aliquando id"
+```
+
+## How source lines alter the document tree {-}
+
+Each line that is processed has an effect on this tree. The line is
+analyzed and, depending on its contents, the document may be altered
+in one or more of the following ways:
+
+1. One or more open blocks may be closed.
+2. One or more new blocks may be created as children of the
+ last open block.
+3. Text may be added to the last (deepest) open block remaining
+ on the tree.
+
+Once a line has been incorporated into the tree in this way,
+it can be discarded, so input can be read in a stream.
+
+We can see how this works by considering how the tree above is
+generated by four lines of Markdown:
+
+``` markdown
+> Lorem ipsum dolor
+sit amet.
+> - Qui *quodsi iracundia*
+> - aliquando id
+```
+
+At the outset, our document model is just
+
+``` tree
+-> document
+```
+
+The first line of our text,
+
+``` markdown
+> Lorem ipsum dolor
+```
+
+causes a `block_quote` block to be created as a child of our
+open `document` block, and a `paragraph` block as a child of
+the `block_quote`. Then the text is added to the last open
+block, the `paragraph`:
+
+``` tree
+-> document
+ -> block_quote
+ -> paragraph
+ "Lorem ipsum dolor"
+```
+
+The next line,
+
+``` markdown
+sit amet.
+```
+
+is a "lazy continuation" of the open `paragraph`, so it gets added
+to the paragraph's text:
+
+``` tree
+-> document
+ -> block_quote
+ -> paragraph
+ "Lorem ipsum dolor\nsit amet."
+```
+
+The third line,
+
+``` markdown
+> - Qui *quodsi iracundia*
+```
+
+causes the `paragraph` block to be closed, and a new `list` block
+opened as a child of the `block_quote`. A `list_item` is also
+added as a child of the `list`, and a `paragraph` as a child of
+the `list_item`. The text is then added to the new `paragraph`:
+
+``` tree
+-> document
+ -> block_quote
+ paragraph
+ "Lorem ipsum dolor\nsit amet."
+ -> list (type=bullet tight=true bullet_char=-)
+ -> list_item
+ -> paragraph
+ "Qui *quodsi iracundia*"
+```
+
+The fourth line,
+
+``` markdown
+> - aliquando id
+```
+
+causes the `list_item` (and its child the `paragraph`) to be closed,
+and a new `list_item` opened up as child of the `list`. A `paragraph`
+is added as a child of the new `list_item`, to contain the text.
+We thus obtain the final tree:
+
+``` tree
+-> document
+ -> block_quote
+ paragraph
+ "Lorem ipsum dolor\nsit amet."
+ -> list (type=bullet tight=true bullet_char=-)
+ list_item
+ paragraph
+ "Qui *quodsi iracundia*"
+ -> list_item
+ -> paragraph
+ "aliquando id"
+```
+
+## From block structure to the final document {-}
+
+Once all of the input has been parsed, all open blocks are closed.
+
+We then "walk the tree," visiting every node, and parse raw
+string contents of paragraphs and headers as inlines. At this
+point we have seen all the link reference definitions, so we can
+resolve reference links as we go.
+
+``` tree
+document
+ block_quote
+ paragraph
+ str "Lorem ipsum dolor"
+ softbreak
+ str "sit amet."
+ list (type=bullet tight=true bullet_char=-)
+ list_item
+ paragraph
+ str "Qui "
+ emph
+ str "quodsi iracundia"
+ list_item
+ paragraph
+ str "aliquando id"
+```
+
+Notice how the newline in the first paragraph has been parsed as
+a `softbreak`, and the asterisks in the first list item have become
+an `emph`.
+
+The document can be rendered as HTML, or in any other format, given
+an appropriate renderer.
+
+
diff --git a/core/testdata/packagedocs/stdlib.md b/core/testdata/packagedocs/stdlib.md
new file mode 100644
index 00000000..5d7432b5
--- /dev/null
+++ b/core/testdata/packagedocs/stdlib.md
@@ -0,0 +1,11 @@
+# Module stdlib
+
+## Kotlin Standard Library
+
+The Kotlin standard library is a set of functions and types implementing idiomatic patterns when working with collections,
+text and files.
+
+# Package kotlin
+
+Core functions and types
+
diff --git a/core/testdata/packages/dottedNamePackage.kt b/core/testdata/packages/dottedNamePackage.kt
new file mode 100644
index 00000000..38619310
--- /dev/null
+++ b/core/testdata/packages/dottedNamePackage.kt
@@ -0,0 +1 @@
+package dot.name \ No newline at end of file
diff --git a/core/testdata/packages/rootPackage.kt b/core/testdata/packages/rootPackage.kt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/core/testdata/packages/rootPackage.kt
diff --git a/core/testdata/packages/simpleNamePackage.kt b/core/testdata/packages/simpleNamePackage.kt
new file mode 100644
index 00000000..2c29f4c7
--- /dev/null
+++ b/core/testdata/packages/simpleNamePackage.kt
@@ -0,0 +1 @@
+package simple
diff --git a/core/testdata/packages/simpleNamePackage2.kt b/core/testdata/packages/simpleNamePackage2.kt
new file mode 100644
index 00000000..2c29f4c7
--- /dev/null
+++ b/core/testdata/packages/simpleNamePackage2.kt
@@ -0,0 +1 @@
+package simple
diff --git a/core/testdata/properties/annotatedProperty.kt b/core/testdata/properties/annotatedProperty.kt
new file mode 100644
index 00000000..8990af29
--- /dev/null
+++ b/core/testdata/properties/annotatedProperty.kt
@@ -0,0 +1 @@
+@Volatile var property = "test" \ No newline at end of file
diff --git a/core/testdata/properties/propertyOverride.kt b/core/testdata/properties/propertyOverride.kt
new file mode 100644
index 00000000..625d1da0
--- /dev/null
+++ b/core/testdata/properties/propertyOverride.kt
@@ -0,0 +1,7 @@
+open class Foo() {
+ open val xyzzy: Int get() = 0
+}
+
+class Bar(): Foo() {
+ override val xyzzy: Int get() = 1
+}
diff --git a/core/testdata/properties/propertyWithReceiver.kt b/core/testdata/properties/propertyWithReceiver.kt
new file mode 100644
index 00000000..e282f6bd
--- /dev/null
+++ b/core/testdata/properties/propertyWithReceiver.kt
@@ -0,0 +1,2 @@
+val String.foobar: Int
+ get() = size() * 2
diff --git a/core/testdata/properties/valueProperty.kt b/core/testdata/properties/valueProperty.kt
new file mode 100644
index 00000000..b87cce57
--- /dev/null
+++ b/core/testdata/properties/valueProperty.kt
@@ -0,0 +1 @@
+val property = "test" \ No newline at end of file
diff --git a/core/testdata/properties/valuePropertyWithGetter.kt b/core/testdata/properties/valuePropertyWithGetter.kt
new file mode 100644
index 00000000..64d3848c
--- /dev/null
+++ b/core/testdata/properties/valuePropertyWithGetter.kt
@@ -0,0 +1,2 @@
+val property: String
+ get() = "test" \ No newline at end of file
diff --git a/core/testdata/properties/variableProperty.kt b/core/testdata/properties/variableProperty.kt
new file mode 100644
index 00000000..54ab4595
--- /dev/null
+++ b/core/testdata/properties/variableProperty.kt
@@ -0,0 +1 @@
+var property = "test" \ No newline at end of file
diff --git a/core/testdata/properties/variablePropertyWithAccessors.kt b/core/testdata/properties/variablePropertyWithAccessors.kt
new file mode 100644
index 00000000..152fb7d0
--- /dev/null
+++ b/core/testdata/properties/variablePropertyWithAccessors.kt
@@ -0,0 +1,4 @@
+var property: String
+ get() = "test"
+ set(value) {
+ } \ No newline at end of file