diff options
121 files changed, 3026 insertions, 788 deletions
@@ -21,7 +21,7 @@ buildNumber.properties *.war *.ear -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm @@ -1,4 +1,4 @@ -dokka [![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +dokka [![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![TeamCity (build status)](https://img.shields.io/teamcity/http/teamcity.jetbrains.com/s/Kotlin_Dokka_DokkaAntMavenGradle.svg)](https://teamcity.jetbrains.com/viewType.html?buildTypeId=Kotlin_Dokka_DokkaAntMavenGradle&branch_KotlinTools_Dokka=%3Cdefault%3E&tab=buildTypeStatusDiv) [ ![Download](https://api.bintray.com/packages/kotlin/dokka/dokka/images/download.svg) ](https://bintray.com/kotlin/dokka/dokka/_latestVersion) ===== @@ -26,7 +26,7 @@ apply plugin: 'org.jetbrains.dokka' ``` The plugin adds a task named "dokka" to the project. - + Minimal dokka configuration: ```groovy @@ -37,7 +37,7 @@ dokka { ``` [Output formats](#output_formats) - + The available configuration options are shown below: ```groovy @@ -52,7 +52,7 @@ dokka { } // List of files with module and package documentation - // http://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation + // https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation includes = ['packages.md', 'extra.md'] // The list of files or directories containing sample code (referenced with @sample tags) @@ -100,19 +100,22 @@ dokka { // If provided, Dokka generates "source" links for each declaration. // Repeat for multiple mappings linkMapping { - // Source directory - dir = "src/main/kotlin" + // Unix based directory relative path to the root of the project (where you execute gradle respectively). + dir = "src/main/kotlin" // or simply "./" // URL showing where the source code can be accessed through the web browser - url = "https://github.com/cy6erGn0m/vertx3-lang-kotlin/blob/master/src/main/kotlin" + url = "https://github.com/cy6erGn0m/vertx3-lang-kotlin/blob/master/src/main/kotlin" //remove src/main/kotlin if you use "./" above // Suffix which is used to append the line number to the URL. Use #L for GitHub suffix = "#L" } - // No default documentation link to kotlin-stdlib + // Disable linking to online kotlin-stdlib documentation noStdlibLink = false + // Disable linking to online JDK documentation + noJdkLink = false + // Allows linking to documentation of the project's dependencies (generated with Javadoc or Dokka) // Repeat for multiple links externalDocumentationLink { @@ -157,6 +160,28 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { Please see the [Dokka Gradle example project](https://github.com/JetBrains/kotlin-examples/tree/master/gradle/dokka-gradle-example) for an example. +#### Dokka Runtime +If you are using Gradle plugin and you want to change the version of Dokka, you can do it by setting `dokkaRuntime`: + +```groovy +buildscript { + ... +} + +apply plugin: 'org.jetbrains.dokka' + +repositories { + jcenter() +} + +dependencies { + dokkaRuntime "org.jetbrains.dokka:dokka-fatjar:0.9.18" +} +``` + +#### FAQ +Please see the [FAQ](https://github.com/Kotlin/dokka/wiki/faq). + #### Android If you are using Android there is a separate Gradle plugin. Just make sure you apply the plugin after @@ -250,7 +275,7 @@ The available configuration options are shown below: <cacheRoot>default</cacheRoot> <!-- List of '.md' files with package and module docs --> - <!-- http://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation --> + <!-- https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation --> <includes> <include>packages.md</include> <include>extra.md</include> @@ -263,7 +288,7 @@ The available configuration options are shown below: <!-- Used for linking to JDK, default: 6 --> <jdkVersion>6</jdkVersion> - + <!-- Do not output deprecated members, applies globally, can be overridden by packageOptions --> <skipDeprecated>false</skipDeprecated> <!-- Emit warnings about not documented members, applies globally, also can be overridden by packageOptions --> @@ -297,15 +322,18 @@ The available configuration options are shown below: <!-- Source directory --> <dir>${project.basedir}/src/main/kotlin</dir> <!-- URL showing where the source code can be accessed through the web browser --> - <url>http://github.com/me/myrepo</url> + <url>https://github.com/cy6erGn0m/vertx3-lang-kotlin/blob/master/src/main/kotlin</url> <!-- //remove src/main/kotlin if you use "./" above --> <!--Suffix which is used to append the line number to the URL. Use #L for GitHub --> <urlSuffix>#L</urlSuffix> </link> </sourceLinks> - <!-- No default documentation link to kotlin-stdlib --> + <!-- Disable linking to online kotlin-stdlib documentation --> <noStdlibLink>false</noStdlibLink> + <!-- Disable linking to online JDK documentation --> + <noJdkLink>false</noJdkLink> + <!-- Allows linking to documentation of the project's dependencies (generated with Javadoc or Dokka) --> <externalDocumentationLinks> <link> @@ -365,11 +393,12 @@ The Ant task supports the following attributes: * `<sourceRoot path="src" platforms="JVM" />` - analogue of src, but allows to specify [platforms](#platforms) * `<packageOptions prefix="kotlin" includeNonPublic="false" reportUndocumented="true" skipDeprecated="false"/>` - Per package options for package `kotlin` and sub-packages of it - * `noStdlibLink` - No default documentation link to kotlin-stdlib + * `noStdlibLink` - disable linking to online kotlin-stdlib documentation + * `noJdkLink` - disable linking to online JDK documentation * `<externalDocumentationLink url="https://example.com/docs/" packageListUrl="file:///home/user/localdocs/package-list"/>` - linking to external documentation, packageListUrl should be used if package-list located not in standard location * `cacheRoot` - Use `default` or set to custom path to cache directory to enable package-list caching. When set to `default`, caches stored in $USER_HOME/.cache/dokka - + ### Using the Command Line @@ -387,20 +416,21 @@ Dokka supports the following command line arguments: * `-module` - the name of the module being documented (used as the root directory of the generated documentation) * `-include` - names of files containing the documentation for the module and individual packages * `-nodeprecated` - if set, deprecated elements are not included in the generated documentation - * `-impliedPlatforms` - List of implied platforms (comma-separated) - * `-packageOptions` - List of package options in format `prefix,-deprecated,-privateApi,+warnUndocumented;...` - * `-links` - External documentation links in format `url^packageListUrl^^url2...` - * `-noStdlibLink` - Disable documentation link to stdlib - * `-cacheRoot` - Use `default` or set to custom path to cache directory to enable package-list caching. When set to `default`, caches stored in $USER_HOME/.cache/dokka + * `-impliedPlatforms` - list of implied platforms (comma-separated) + * `-packageOptions` - list of package options in format `prefix,-deprecated,-privateApi,+warnUndocumented;...` + * `-links` - external documentation links in format `url^packageListUrl^^url2...` + * `-noStdlibLink` - disable linking to online kotlin-stdlib documentation + * `-noJdkLink` - disable linking to online JDK documentation + * `-cacheRoot` - use `default` or set to custom path to cache directory to enable package-list caching. When set to `default`, caches stored in $USER_HOME/.cache/dokka ### Output formats<a name="output_formats"></a> - * `html` - minimalistic html format used by default - * `javadoc` - Dokka mimic to javadoc - * `html-as-java` - as `html` but using java syntax - * `markdown` - Markdown structured as `html` - * `gfm` - GitHub flavored markdown + * `html` - minimalistic html format used by default, Java classes are translated to Kotlin + * `javadoc` - looks like normal Javadoc, Kotlin classes are translated to Java + * `html-as-java` - looks like `html`, but Kotlin classes are translated to Java + * `markdown` - markdown structured as `html`, Java classes are translated to Kotlin + * `gfm` - GitHub flavored markdown * `jekyll` - Jekyll compatible markdown * `kotlin-website*` - internal format used for documentation on [kotlinlang.org](https://kotlinlang.org) diff --git a/build.gradle b/build.gradle index 35ec271e..0b5d5dbc 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ allprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } maven { url "https://plugins.gradle.org/m2/" } ivy(repo) @@ -29,7 +29,8 @@ allprojects { repositories { mavenCentral() mavenLocal() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap" } + maven { url "https://dl.bintray.com/jetbrains/markdown" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } maven { url 'https://jitpack.io' } maven { url "https://teamcity.jetbrains.com/guestAuth/repository/download/Kotlin_dev_CompilerAllPlugins/$bundled_kotlin_compiler_version/maven" } @@ -99,4 +100,29 @@ def ideaRT() { return zipTree(project.configurations.ideaIC.singleFile).matching ({ include("lib/idea_rt.jar") }) +} + +def repoLocation = uri(file("$buildDir/dist-maven")) + +allprojects { + + task publishToDistMaven { + group "publishing" + description "Publishes all Maven publications to Maven repository 'distMaven'." + dependsOn tasks.withType(PublishToMavenRepository).matching { + it.repository == publishing.repositories.distMaven + } + } + + plugins.withType(MavenPublishPlugin) { + publishing { + repositories { + maven { + name 'distMaven' + url repoLocation + } + } + } + + } }
\ No newline at end of file diff --git a/buildSrc/src/main/groovy/org/jetbrains/CorrectShadowPublishing.groovy b/buildSrc/src/main/groovy/org/jetbrains/CorrectShadowPublishing.groovy index 3e1d2106..58cfdcf7 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/CorrectShadowPublishing.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/CorrectShadowPublishing.groovy @@ -35,5 +35,5 @@ private static void addDependency(Node dependenciesNode, dep) { dependencyNode.appendNode('groupId', dep.group) dependencyNode.appendNode('artifactId', dep.name) dependencyNode.appendNode('version', dep.version) - dependencyNode.appendNode('scope', 'runtime') -}
\ No newline at end of file + dependencyNode.appendNode('scope', 'compile') +} diff --git a/core/build.gradle b/core/build.gradle index a87d3c97..a4538471 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -29,7 +29,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-script-runtime:$bundled_kotlin_compiler_version" compile "teamcity:kotlin-ide-common:$bundled_kotlin_compiler_version" - compile "teamcity:markdown:$markdownVersion" + compile "org.jetbrains:markdown:$markdownVersion" compile intellijCoreAnalysis() diff --git a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt index 5522d4f0..8ad7b8bb 100644 --- a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt +++ b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt @@ -18,8 +18,12 @@ import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.io.URLUtil import org.jetbrains.kotlin.analyzer.* +import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.config.ContentRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider @@ -29,13 +33,13 @@ import org.jetbrains.kotlin.cli.jvm.config.* import org.jetbrains.kotlin.cli.jvm.index.JavaRoot import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.container.getService +import org.jetbrains.kotlin.container.tryGetService import org.jetbrains.kotlin.context.ProjectContext import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.platform.JvmBuiltIns import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace @@ -72,7 +76,7 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { val projectComponentManager = environment.project as MockComponentManager val projectFileIndex = CoreProjectFileIndex(environment.project, - environment.configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS)) + environment.configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS)) val moduleManager = object : CoreModuleManager(environment.project, this) { override fun getModules(): Array<out Module> = arrayOf(projectFileIndex.module) @@ -98,7 +102,7 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { } - fun createResolutionFacade(environment: KotlinCoreEnvironment): DokkaResolutionFacade { + fun createResolutionFacade(environment: KotlinCoreEnvironment): Pair<DokkaResolutionFacade, DokkaResolutionFacade> { val projectContext = ProjectContext(environment.project) val sourceFiles = environment.getSourceFiles() @@ -131,43 +135,49 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { } val resolverForProject = ResolverForProjectImpl( - "Dokka", - projectContext, - listOf(library, module), - { JvmAnalyzerFacade }, - { - when (it) { - library -> ModuleContent(emptyList(), GlobalSearchScope.notScope(sourcesScope)) - module -> ModuleContent(sourceFiles, sourcesScope) - else -> throw IllegalArgumentException("Unexpected module info") + "Dokka", + projectContext, + listOf(library, module), + { + when (it) { + library -> ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope)) + module -> ModuleContent(it, emptyList(), sourcesScope) + else -> throw IllegalArgumentException("Unexpected module info") + } + }, + { + JvmPlatform.multiTargetPlatform + }, + LanguageSettingsProvider.Default /* TODO: Fix this */, + { JvmAnalyzerFacade }, + { + JvmPlatformParameters ({ content -> + JvmPackagePartProvider(configuration.languageVersionSettings, content.moduleContentScope).apply { + addRoots(javaRoots, messageCollector) } - }, - JvmPlatformParameters { + }, { val file = (it as JavaClassImpl).psi.containingFile.virtualFile if (file in sourcesScope) module else library - }, - CompilerEnvironment, - packagePartProviderFactory = { info, content -> - JvmPackagePartProvider(configuration.languageVersionSettings, content.moduleContentScope).apply { - addRoots(javaRoots) - } - }, - builtIns = builtIns, - modulePlatforms = { JvmPlatform.multiTargetPlatform } + }) + }, + CompilerEnvironment, + builtIns = builtIns ) - resolverForProject.resolverForModule(library) // Required before module to initialize library properly + val resolverForLibrary = resolverForProject.resolverForModule(library) // Required before module to initialize library properly val resolverForModule = resolverForProject.resolverForModule(module) + val libraryModuleDescriptor = resolverForProject.descriptorForModule(library) val moduleDescriptor = resolverForProject.descriptorForModule(module) builtIns.initialize(moduleDescriptor, true) + val libraryResolutionFacade = DokkaResolutionFacade(environment.project, libraryModuleDescriptor, resolverForLibrary) val created = DokkaResolutionFacade(environment.project, moduleDescriptor, resolverForModule) val projectComponentManager = environment.project as MockComponentManager projectComponentManager.registerService(KotlinCacheService::class.java, CoreKotlinCacheService(created)) - return created + return created to libraryResolutionFacade } fun loadLanguageVersionSettings(languageVersionString: String?, apiVersionString: String?) { @@ -202,7 +212,7 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { * List of source roots for this environment. */ val sources: List<String> - get() = configuration.get(JVMConfigurationKeys.CONTENT_ROOTS) + get() = configuration.get(CLIConfigurationKeys.CONTENT_ROOTS) ?.filterIsInstance<KotlinSourceRoot>() ?.map { it.path } ?: emptyList() @@ -221,7 +231,7 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { } fun addRoots(list: List<ContentRoot>) { - configuration.addAll(JVMConfigurationKeys.CONTENT_ROOTS, list) + configuration.addAll(CLIConfigurationKeys.CONTENT_ROOTS, list) } /** @@ -234,15 +244,19 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { fun contentRootFromPath(path: String): ContentRoot { val file = File(path) - return if (file.extension == "java") JavaSourceRoot(file, null) else KotlinSourceRoot(path) + return if (file.extension == "java") JavaSourceRoot(file, null) else KotlinSourceRoot(path, false) } class DokkaResolutionFacade(override val project: Project, override val moduleDescriptor: ModuleDescriptor, val resolverForModule: ResolverForModule) : ResolutionFacade { + override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult { + throw UnsupportedOperationException() + } + override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? { - return null + return resolverForModule.componentProvider.tryGetService(serviceClass) } override fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode): DeclarationDescriptor { @@ -295,10 +309,6 @@ class DokkaResolutionFacade(override val project: Project, throw UnsupportedOperationException() } - override fun analyzeFullyAndGetResult(elements: Collection<KtElement>): AnalysisResult { - throw UnsupportedOperationException() - } - override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T { throw UnsupportedOperationException() } diff --git a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt index 4f6a7c76..319d85b1 100644 --- a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt +++ b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt @@ -20,10 +20,10 @@ import com.intellij.openapi.vfs.VirtualFileFilter import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.messages.MessageBus import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.cli.common.config.ContentRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot 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 diff --git a/core/src/main/kotlin/Analysis/JavaResolveExtension.kt b/core/src/main/kotlin/Analysis/JavaResolveExtension.kt new file mode 100644 index 00000000..4dc6b366 --- /dev/null +++ b/core/src/main/kotlin/Analysis/JavaResolveExtension.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("JavaResolutionUtils") + +package org.jetbrains.dokka + +import com.intellij.psi.* +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.load.java.sources.JavaSourceElement +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.load.java.structure.impl.* +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.psiUtil.parameterIndex +import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.MemberScope + +// TODO: Remove that file + +@JvmOverloads +fun PsiMethod.getJavaMethodDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? { + val method = originalElement as? PsiMethod ?: return null + if (method.containingClass == null || !Name.isValidIdentifier(method.name)) return null + val resolver = method.getJavaDescriptorResolver(resolutionFacade) + return when { + method.isConstructor -> resolver?.resolveConstructor(JavaConstructorImpl(method)) + else -> resolver?.resolveMethod(JavaMethodImpl(method)) + } +} + +@JvmOverloads +fun PsiClass.getJavaClassDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): ClassDescriptor? { + val psiClass = originalElement as? PsiClass ?: return null + return psiClass.getJavaDescriptorResolver(resolutionFacade)?.resolveClass(JavaClassImpl(psiClass)) +} + +@JvmOverloads +fun PsiField.getJavaFieldDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): PropertyDescriptor? { + val field = originalElement as? PsiField ?: return null + return field.getJavaDescriptorResolver(resolutionFacade)?.resolveField(JavaFieldImpl(field)) +} + +@JvmOverloads +fun PsiMember.getJavaMemberDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? { + return when (this) { + is PsiEnumConstant -> containingClass?.getJavaClassDescriptor(resolutionFacade) + is PsiClass -> getJavaClassDescriptor(resolutionFacade) + is PsiMethod -> getJavaMethodDescriptor(resolutionFacade) + is PsiField -> getJavaFieldDescriptor(resolutionFacade) + else -> null + } +} + +@JvmOverloads +fun PsiMember.getJavaOrKotlinMemberDescriptor(resolutionFacade: ResolutionFacade = javaResolutionFacade()): DeclarationDescriptor? { + val callable = unwrapped + return when (callable) { + is PsiMember -> getJavaMemberDescriptor(resolutionFacade) + is KtDeclaration -> { + val descriptor = resolutionFacade.resolveToDescriptor(callable) + if (descriptor is ClassDescriptor && this is PsiMethod) descriptor.unsubstitutedPrimaryConstructor else descriptor + } + else -> null + } +} + +private fun PsiElement.getJavaDescriptorResolver(resolutionFacade: ResolutionFacade): JavaDescriptorResolver? { + return resolutionFacade.tryGetFrontendService(this, JavaDescriptorResolver::class.java) +} + +private fun JavaDescriptorResolver.resolveMethod(method: JavaMethod): DeclarationDescriptor? { + return getContainingScope(method) + ?.getContributedDescriptors(nameFilter = { true }, kindFilter = DescriptorKindFilter.CALLABLES) + ?.filterIsInstance<DeclarationDescriptorWithSource>() + ?.findByJavaElement(method) +} + +private fun JavaDescriptorResolver.resolveConstructor(constructor: JavaConstructor): ConstructorDescriptor? { + return resolveClass(constructor.containingClass)?.constructors?.findByJavaElement(constructor) +} + +private fun JavaDescriptorResolver.resolveField(field: JavaField): PropertyDescriptor? { + return getContainingScope(field)?.getContributedVariables(field.name, NoLookupLocation.FROM_IDE)?.findByJavaElement(field) +} + +private fun JavaDescriptorResolver.getContainingScope(member: JavaMember): MemberScope? { + val containingClass = resolveClass(member.containingClass) + return if (member.isStatic) + containingClass?.staticScope + else + containingClass?.defaultType?.memberScope +} + +private fun <T : DeclarationDescriptorWithSource> Collection<T>.findByJavaElement(javaElement: JavaElement): T? { + return firstOrNull { member -> + val memberJavaElement = (member.original.source as? JavaSourceElement)?.javaElement + when { + memberJavaElement == javaElement -> + true + memberJavaElement is JavaElementImpl<*> && javaElement is JavaElementImpl<*> -> + memberJavaElement.psi.isEquivalentTo(javaElement.psi) + else -> + false + } + } +} + +fun PsiElement.javaResolutionFacade() = + KotlinCacheService.getInstance(project).getResolutionFacadeByFile(this.originalElement.containingFile, JvmPlatform)!! diff --git a/core/src/main/kotlin/DokkaBootstrapImpl.kt b/core/src/main/kotlin/DokkaBootstrapImpl.kt index aeaca8be..e18ab6cf 100644 --- a/core/src/main/kotlin/DokkaBootstrapImpl.kt +++ b/core/src/main/kotlin/DokkaBootstrapImpl.kt @@ -52,24 +52,26 @@ class DokkaBootstrapImpl : DokkaBootstrap { includes, moduleName, DocumentationOptions( - outputDir, - format, - includeNonPublic, - includeRootPackage, - reportUndocumented, - skipEmptyPackages, - skipDeprecated, - jdkVersion, - generateIndexPages, - sourceLinks, - impliedPlatforms, - perPackageOptions, - externalDocumentationLinks, - noStdlibLink, - languageVersion, - apiVersion, - cacheRoot, - suppressedFiles.map { File(it) }.toSet() + outputDir = outputDir, + outputFormat = format, + includeNonPublic = includeNonPublic, + includeRootPackage = includeRootPackage, + reportUndocumented = reportUndocumented, + skipEmptyPackages = skipEmptyPackages, + skipDeprecated = skipDeprecated, + jdkVersion = jdkVersion, + generateIndexPages = generateIndexPages, + sourceLinks = sourceLinks, + impliedPlatforms = impliedPlatforms, + perPackageOptions = perPackageOptions, + externalDocumentationLinks = externalDocumentationLinks, + noStdlibLink = noStdlibLink, + noJdkLink = noJdkLink, + languageVersion = languageVersion, + apiVersion = apiVersion, + cacheRoot = cacheRoot, + suppressedFiles = suppressedFiles.map { File(it) }.toSet(), + collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries ) ) } diff --git a/core/src/main/kotlin/Formats/AnalysisComponents.kt b/core/src/main/kotlin/Formats/AnalysisComponents.kt index c4d97dbb..d78d4a0c 100644 --- a/core/src/main/kotlin/Formats/AnalysisComponents.kt +++ b/core/src/main/kotlin/Formats/AnalysisComponents.kt @@ -2,9 +2,9 @@ package org.jetbrains.dokka.Formats import com.google.inject.Binder import org.jetbrains.dokka.* -import org.jetbrains.dokka.Kotlin.KotlinAsJavaDescriptorSignatureProvider -import org.jetbrains.dokka.Kotlin.KotlinDescriptorSignatureProvider -import org.jetbrains.dokka.Model.DescriptorSignatureProvider +import org.jetbrains.dokka.KotlinAsJavaElementSignatureProvider +import org.jetbrains.dokka.KotlinElementSignatureProvider +import org.jetbrains.dokka.ElementSignatureProvider import org.jetbrains.dokka.Samples.DefaultSampleProcessingService import org.jetbrains.dokka.Samples.SampleProcessingService import org.jetbrains.dokka.Utilities.bind @@ -16,12 +16,12 @@ interface DefaultAnalysisComponentServices { val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder> val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder> val sampleProcessingService: KClass<out SampleProcessingService> - val descriptorSignatureProvider: KClass<out DescriptorSignatureProvider> + val elementSignatureProvider: KClass<out ElementSignatureProvider> } interface DefaultAnalysisComponent : FormatDescriptorAnalysisComponent, DefaultAnalysisComponentServices { override fun configureAnalysis(binder: Binder): Unit = with(binder) { - bind<DescriptorSignatureProvider>() toType descriptorSignatureProvider + bind<ElementSignatureProvider>() toType elementSignatureProvider bind<PackageDocumentationBuilder>() toType packageDocumentationBuilderClass bind<JavaDocumentationBuilder>() toType javaDocumentationBuilderClass bind<SampleProcessingService>() toType sampleProcessingService @@ -33,7 +33,7 @@ object KotlinAsJava : DefaultAnalysisComponentServices { override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class override val sampleProcessingService = DefaultSampleProcessingService::class - override val descriptorSignatureProvider = KotlinAsJavaDescriptorSignatureProvider::class + override val elementSignatureProvider = KotlinAsJavaElementSignatureProvider::class } @@ -41,5 +41,5 @@ object KotlinAsKotlin : DefaultAnalysisComponentServices { override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class override val sampleProcessingService = DefaultSampleProcessingService::class - override val descriptorSignatureProvider = KotlinDescriptorSignatureProvider::class + override val elementSignatureProvider = KotlinElementSignatureProvider::class }
\ No newline at end of file diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt new file mode 100644 index 00000000..09bb2602 --- /dev/null +++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt @@ -0,0 +1,117 @@ +package org.jetbrains.dokka.Formats + +import org.jetbrains.dokka.* +import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject +import org.jetbrains.kotlin.types.KotlinType + +class JavaLayoutHtmlPackageListService: PackageListService { + + private fun StringBuilder.appendParam(name: String, value: String) { + append(DOKKA_PARAM_PREFIX) + append(name) + append(":") + appendln(value) + } + + override fun formatPackageList(module: DocumentationModule): String { + val packages = module.members(NodeKind.Package).map { it.name } + + return buildString { + appendParam("format", "java-layout-html") + appendParam("mode", "kotlin") + for (p in packages) { + appendln(p) + } + } + } + +} + +class JavaLayoutHtmlInboundLinkResolutionService(private val paramMap: Map<String, List<String>>) : InboundExternalLinkResolutionService { + private fun getContainerPath(symbol: DeclarationDescriptor): String? { + return when (symbol) { + is PackageFragmentDescriptor -> symbol.fqName.asString().replace('.', '/') + "/" + is ClassifierDescriptor -> getContainerPath(symbol.findPackage()) + symbol.nameWithOuter() + ".html" + else -> null + } + } + + private fun DeclarationDescriptor.findPackage(): PackageFragmentDescriptor = + generateSequence(this) { it.containingDeclaration }.filterIsInstance<PackageFragmentDescriptor>().first() + + private fun ClassifierDescriptor.nameWithOuter(): String = + generateSequence(this) { it.containingDeclaration as? ClassifierDescriptor } + .toList().asReversed().joinToString(".") { it.name.asString() } + + private fun getPagePath(symbol: DeclarationDescriptor): String? { + return when (symbol) { + is PackageFragmentDescriptor -> getContainerPath(symbol) + "package-summary.html" + is EnumEntrySyntheticClassDescriptor -> getContainerPath(symbol.containingDeclaration) + "#" + symbol.signatureForAnchorUrlEncoded() + is ClassifierDescriptor -> getContainerPath(symbol) + "#" + is FunctionDescriptor, is PropertyDescriptor -> getContainerPath(symbol.containingDeclaration!!) + "#" + symbol.signatureForAnchorUrlEncoded() + else -> null + } + } + + private fun DeclarationDescriptor.signatureForAnchor(): String? { + + fun ReceiverParameterDescriptor.extractReceiverName(): String { + var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!! + if (receiverClass.isCompanionObject()) { + receiverClass = receiverClass.containingDeclaration!! + } else if (receiverClass is TypeParameterDescriptor) { + val upperBoundClass = receiverClass.upperBounds.singleOrNull()?.constructor?.declarationDescriptor + if (upperBoundClass != null) { + receiverClass = upperBoundClass + } + } + + return receiverClass.name.asString() + } + + fun KotlinType.qualifiedNameForSignature(): String { + val desc = constructor.declarationDescriptor + return desc?.fqNameUnsafe?.asString() ?: "<ERROR TYPE NAME>" + } + + fun StringBuilder.appendReceiverAndCompanion(desc: CallableDescriptor) { + if (desc.containingDeclaration.isCompanionObject()) { + append("Companion.") + } + desc.extensionReceiverParameter?.let { + append("(") + append(it.extractReceiverName()) + append(").") + } + } + + return when(this) { + is EnumEntrySyntheticClassDescriptor -> buildString { + append("ENUM_VALUE:") + append(name.asString()) + } + is FunctionDescriptor -> buildString { + appendReceiverAndCompanion(this@signatureForAnchor) + append(name.asString()) + valueParameters.joinTo(this, prefix = "(", postfix = ")") { + it.type.qualifiedNameForSignature() + } + } + is PropertyDescriptor -> buildString { + appendReceiverAndCompanion(this@signatureForAnchor) + append(name.asString()) + append(":") + append(returnType?.qualifiedNameForSignature()) + } + else -> null + } + } + + private fun DeclarationDescriptor.signatureForAnchorUrlEncoded(): String? = signatureForAnchor()?.urlEncoded() + + override fun getPath(symbol: DeclarationDescriptor) = getPagePath(symbol) +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt index f73cd23e..885cdf6c 100644 --- a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt +++ b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt @@ -6,7 +6,6 @@ import kotlinx.html.li import kotlinx.html.stream.appendHTML import kotlinx.html.ul import org.jetbrains.dokka.* -import org.jetbrains.dokka.Kotlin.KotlinDescriptorSignatureProvider import org.jetbrains.dokka.Samples.DefaultSampleProcessingService import org.jetbrains.dokka.Utilities.bind import org.jetbrains.dokka.Utilities.toType @@ -17,7 +16,7 @@ class JavaLayoutHtmlFormatDescriptor : FormatDescriptor, DefaultAnalysisComponen override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class override val sampleProcessingService = DefaultSampleProcessingService::class - override val descriptorSignatureProvider = KotlinDescriptorSignatureProvider::class + override val elementSignatureProvider = KotlinElementSignatureProvider::class override fun configureOutput(binder: Binder): Unit = with(binder) { bind<Generator>() toType generatorServiceClass diff --git a/core/src/main/kotlin/Formats/OutlineService.kt b/core/src/main/kotlin/Formats/OutlineService.kt index 3c31ba57..958e93af 100644 --- a/core/src/main/kotlin/Formats/OutlineService.kt +++ b/core/src/main/kotlin/Formats/OutlineService.kt @@ -16,7 +16,7 @@ interface OutlineFormatService { for (node in nodes) { appendOutlineHeader(location, node, to) if (node.members.any()) { - val sortedMembers = node.members.sortedBy { it.name } + val sortedMembers = node.members.sortedBy { it.name.toLowerCase() } appendOutlineLevel(to) { appendOutline(location, to, sortedMembers) } diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt index 952e14cf..410de281 100644 --- a/core/src/main/kotlin/Formats/StructuredFormatService.kt +++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt @@ -152,6 +152,10 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } } + is NodeRenderContent -> { + val node = content.node + appendContent(languageService.render(node, content.mode)) + } is ContentNodeLink -> { val node = content.node val linkTo = if (node != null) locationHref(location, node) else "#" @@ -570,7 +574,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendHeader(3) { appendText(caption) } - val children = if (sortMembers) members.sortedBy { it.name } else members + val children = if (sortMembers) members.sortedBy { it.name.toLowerCase() } else members val membersMap = children.groupBy { link(node, it) } diff --git a/core/src/main/kotlin/Generation/DokkaGenerator.kt b/core/src/main/kotlin/Generation/DokkaGenerator.kt index 09e5cedf..a5279772 100644 --- a/core/src/main/kotlin/Generation/DokkaGenerator.kt +++ b/core/src/main/kotlin/Generation/DokkaGenerator.kt @@ -10,13 +10,13 @@ import com.intellij.psi.PsiManager import org.jetbrains.dokka.DokkaConfiguration.SourceRoot import org.jetbrains.dokka.Utilities.DokkaAnalysisModule import org.jetbrains.dokka.Utilities.DokkaOutputModule +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys 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.JVMConfigurationKeys import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer import org.jetbrains.kotlin.resolve.TopDownAnalysisMode @@ -157,9 +157,13 @@ fun buildDocumentationModule(injector: Injector, } } + parseJavaPackageDocs(packageDocs, coreEnvironment) + with(injector.getInstance(DocumentationBuilder::class.java)) { documentationModule.appendFragments(fragments, packageDocs.packageContent, injector.getInstance(PackageDocumentationBuilder::class.java)) + + propagateExtensionFunctionsToSubclasses(fragments, resolutionFacade) } val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter) @@ -168,9 +172,21 @@ fun buildDocumentationModule(injector: Injector, } } +fun parseJavaPackageDocs(packageDocs: PackageDocs, coreEnvironment: KotlinCoreEnvironment) { + val contentRoots = coreEnvironment.configuration.get(CLIConfigurationKeys.CONTENT_ROOTS) + ?.filterIsInstance<JavaSourceRoot>() + ?.map { it.file } + ?: listOf() + contentRoots.forEach { root -> + root.walkTopDown().filter { it.name == "overview.html" }.forEach { + packageDocs.parseJava(it.path, it.relativeTo(root).parent.replace("/", ".")) + } + } +} + fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> { - val sourceRoots = configuration.get(JVMConfigurationKeys.CONTENT_ROOTS) + val sourceRoots = configuration.get(CLIConfigurationKeys.CONTENT_ROOTS) ?.filterIsInstance<JavaSourceRoot>() ?.map { it.file } ?: listOf() diff --git a/core/src/main/kotlin/Generation/configurationImpl.kt b/core/src/main/kotlin/Generation/configurationImpl.kt index 34d4154e..eecf122e 100644 --- a/core/src/main/kotlin/Generation/configurationImpl.kt +++ b/core/src/main/kotlin/Generation/configurationImpl.kt @@ -11,9 +11,9 @@ data class SourceLinkDefinitionImpl(override val path: String, companion object { fun parseSourceLinkDefinition(srcLink: String): SourceLinkDefinition { val (path, urlAndLine) = srcLink.split('=') - return SourceLinkDefinitionImpl(File(path).absolutePath, + return SourceLinkDefinitionImpl(File(path).canonicalPath, urlAndLine.substringBefore("#"), - urlAndLine.substringAfter("#", "").let { if (it.isEmpty()) null else "#" + it }) + urlAndLine.substringAfter("#", "").let { if (it.isEmpty()) null else "#$it" }) } } } @@ -36,27 +36,29 @@ data class PackageOptionsImpl(override val prefix: String, override val suppress: Boolean = false) : DokkaConfiguration.PackageOptions data class DokkaConfigurationImpl( - override val moduleName: String, - override val classpath: List<String>, - override val sourceRoots: List<SourceRootImpl>, - override val samples: List<String>, - override val includes: List<String>, - override val outputDir: String, - override val format: String, - override val includeNonPublic: Boolean, - override val includeRootPackage: Boolean, - override val reportUndocumented: Boolean, - override val skipEmptyPackages: Boolean, - override val skipDeprecated: Boolean, - override val jdkVersion: Int, - override val generateIndexPages: Boolean, - override val sourceLinks: List<SourceLinkDefinitionImpl>, - override val impliedPlatforms: List<String>, - override val perPackageOptions: List<PackageOptionsImpl>, - override val externalDocumentationLinks: List<ExternalDocumentationLinkImpl>, - override val noStdlibLink: Boolean, - override val cacheRoot: String?, - override val suppressedFiles: List<String>, - override val languageVersion: String?, - override val apiVersion: String? + override val moduleName: String, + override val classpath: List<String>, + override val sourceRoots: List<SourceRootImpl>, + override val samples: List<String>, + override val includes: List<String>, + override val outputDir: String, + override val format: String, + override val includeNonPublic: Boolean, + override val includeRootPackage: Boolean, + override val reportUndocumented: Boolean, + override val skipEmptyPackages: Boolean, + override val skipDeprecated: Boolean, + override val jdkVersion: Int, + override val generateIndexPages: Boolean, + override val sourceLinks: List<SourceLinkDefinitionImpl>, + override val impliedPlatforms: List<String>, + override val perPackageOptions: List<PackageOptionsImpl>, + override val externalDocumentationLinks: List<ExternalDocumentationLinkImpl>, + override val noStdlibLink: Boolean, + override val noJdkLink: Boolean, + override val cacheRoot: String?, + override val suppressedFiles: List<String>, + override val languageVersion: String?, + override val apiVersion: String?, + override val collectInheritedExtensionsFromLibraries: Boolean ) : DokkaConfiguration
\ No newline at end of file diff --git a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt index cf2b0514..f1f170d7 100644 --- a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt +++ b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt @@ -1,9 +1,12 @@ package org.jetbrains.dokka import com.google.inject.Inject +import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* +import com.intellij.psi.impl.JavaConstantExpressionEvaluator import com.intellij.psi.util.InheritanceUtil import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag @@ -14,14 +17,23 @@ import org.jetbrains.kotlin.psi.KtModifierListOwner import java.io.File fun getSignature(element: PsiElement?) = when(element) { + is PsiPackage -> element.qualifiedName is PsiClass -> element.qualifiedName is PsiField -> element.containingClass!!.qualifiedName + "$" + element.name is PsiMethod -> - element.containingClass!!.qualifiedName + "$" + element.name + "(" + - element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" + methodSignature(element) + is PsiParameter -> { + val method = (element.parent.parent as PsiMethod) + methodSignature(method) + } else -> null } +private fun methodSignature(method: PsiMethod): String { + return method.containingClass!!.qualifiedName + "$" + method.name + "(" + + method.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" +} + private fun PsiType.typeSignature(): String = when(this) { is PsiArrayType -> "Array((${componentType.typeSignature()}))" is PsiPrimitiveType -> "kotlin." + canonicalText.capitalize() @@ -45,10 +57,16 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { private val refGraph: NodeReferenceGraph private val docParser: JavaDocumentationParser - @Inject constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, logger: DokkaLogger) { + @Inject constructor( + options: DocumentationOptions, + refGraph: NodeReferenceGraph, + logger: DokkaLogger, + signatureProvider: ElementSignatureProvider, + externalDocumentationLinkResolver: ExternalDocumentationLinkResolver + ) { this.options = options this.refGraph = refGraph - this.docParser = JavadocParser(refGraph, logger) + this.docParser = JavadocParser(refGraph, logger, signatureProvider, externalDocumentationLinkResolver) } constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) { @@ -61,7 +79,7 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { if (skipFile(file) || file.classes.all { skipElement(it) }) { return } - val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap(), refGraph) + val packageNode = findOrCreatePackageNode(module, file.packageName, emptyMap(), refGraph) appendClasses(packageNode, file.classes) } @@ -92,9 +110,11 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { fun nodeForElement(element: PsiNamedElement, kind: NodeKind, - name: String = element.name ?: "<anonymous>"): DocumentationNode { + name: String = element.name ?: "<anonymous>", + register: Boolean = false): DocumentationNode { val (docComment, deprecatedContent) = docParser.parseDocumentation(element) val node = DocumentationNode(name, docComment, kind) + if (register) register(element, node) if (element is PsiModifierListOwner) { node.appendModifiers(element) val modifierList = element.modifierList @@ -139,11 +159,13 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { hasSuppressDocTag(element) || skipElementBySuppressedFiles(element) - private fun skipElementByVisibility(element: Any): Boolean = element is PsiModifierListOwner && - !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) && - (element.hasModifierProperty(PsiModifier.PRIVATE) || - element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || - element.isInternal()) + private fun skipElementByVisibility(element: Any): Boolean = + element is PsiModifierListOwner && + element !is PsiParameter && + !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) && + (element.hasModifierProperty(PsiModifier.PRIVATE) || + element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || + element.isInternal()) private fun skipElementBySuppressedFiles(element: Any): Boolean = element is PsiElement && File(element.containingFile.virtualFile.path).absoluteFile in options.suppressedFiles @@ -161,13 +183,13 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { fun PsiClass.build(): DocumentationNode { val kind = when { + isAnnotationType -> NodeKind.AnnotationClass isInterface -> NodeKind.Interface isEnum -> NodeKind.Enum - isAnnotationType -> NodeKind.AnnotationClass isException() -> NodeKind.Exception else -> NodeKind.Class } - val node = nodeForElement(this, kind) + val node = nodeForElement(this, kind, register = isAnnotationType) superTypes.filter { !ignoreSupertype(it) }.forEach { node.appendType(it, NodeKind.Supertype) val superClass = it.resolve() @@ -200,11 +222,28 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { fun PsiField.build(): DocumentationNode { val node = nodeForElement(this, nodeKind()) node.appendType(type) - node.appendModifiers(this) + + node.appendConstantValueIfAny(this) register(this, node) return node } + private fun DocumentationNode.appendConstantValueIfAny(field: PsiField) { + val modifierList = field.modifierList ?: return + val initializer = field.initializer ?: return + if (modifierList.hasExplicitModifier(PsiModifier.FINAL) && + modifierList.hasExplicitModifier(PsiModifier.STATIC)) { + val value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false) + val text = when(value) { + null -> return // No value found + is String -> + "\"" + StringUtil.escapeStringCharacters(value) + "\"" + else -> value.toString() + } + append(DocumentationNode(text, Content.Empty, NodeKind.Value), RefKind.Detail) + } + } + private fun PsiField.nodeKind(): NodeKind = when { this is PsiEnumConstant -> NodeKind.EnumItem else -> NodeKind.Field @@ -274,8 +313,26 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { return node } + private fun lookupOrBuildClass(psiClass: PsiClass): DocumentationNode { + val existing = refGraph.lookup(getSignature(psiClass)!!) + if (existing != null) return existing + val new = psiClass.build() + val packageNode = findOrCreatePackageNode(null, (psiClass.parent as PsiJavaFile).packageName, emptyMap(), refGraph) + packageNode.append(new, RefKind.Member) + return new + } + fun PsiAnnotation.build(): DocumentationNode { - val node = DocumentationNode(nameReferenceElement?.text ?: "<?>", Content.Empty, NodeKind.Annotation) + + val original = when (this) { + is KtLightAbstractAnnotation -> clsDelegate + else -> this + } + val node = DocumentationNode(qualifiedName?.substringAfterLast(".") ?: "<?>", Content.Empty, NodeKind.Annotation) + val psiClass = original.nameReferenceElement?.resolve() as? PsiClass + if (psiClass != null && psiClass.isAnnotationType) { + node.append(lookupOrBuildClass(psiClass), RefKind.Link) + } parameterList.attributes.forEach { val parameter = DocumentationNode(it.name ?: "value", Content.Empty, NodeKind.Parameter) val value = it.value diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt index c25f5813..70af73f9 100644 --- a/core/src/main/kotlin/Java/JavadocParser.kt +++ b/core/src/main/kotlin/Java/JavadocParser.kt @@ -1,14 +1,16 @@ package org.jetbrains.dokka import com.intellij.psi.* -import com.intellij.psi.javadoc.PsiDocTag -import com.intellij.psi.javadoc.PsiDocTagValue -import com.intellij.psi.javadoc.PsiDocToken -import com.intellij.psi.javadoc.PsiInlineDocTag +import com.intellij.psi.impl.source.tree.JavaDocElementType +import com.intellij.psi.javadoc.* +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.containers.isNullOrEmpty +import org.jetbrains.kotlin.utils.keysToMap import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.jsoup.nodes.Node import org.jsoup.nodes.TextNode +import java.net.URI data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) { companion object { @@ -20,75 +22,163 @@ interface JavaDocumentationParser { fun parseDocumentation(element: PsiNamedElement): JavadocParseResult } -class JavadocParser(private val refGraph: NodeReferenceGraph, - private val logger: DokkaLogger) : JavaDocumentationParser { +class JavadocParser( + private val refGraph: NodeReferenceGraph, + private val logger: DokkaLogger, + private val signatureProvider: ElementSignatureProvider, + private val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver +) : JavaDocumentationParser { + + private fun ContentSection.appendTypeElement(signature: String, selector: (DocumentationNode) -> DocumentationNode?) { + append(LazyContentBlock { + val node = refGraph.lookupOrWarn(signature, logger)?.let(selector) ?: return@LazyContentBlock emptyList() + listOf(ContentBlock().apply { + append(NodeRenderContent(node, LanguageService.RenderMode.SUMMARY)) + symbol(":") + text(" ") + }) + }) + } + override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { - val docComment = (element as? PsiDocCommentOwner)?.docComment - if (docComment == null) return JavadocParseResult.Empty + val docComment = (element as? PsiDocCommentOwner)?.docComment ?: return JavadocParseResult.Empty val result = MutableContent() var deprecatedContent: Content? = null - val para = ContentParagraph() - result.append(para) - para.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }) + + val nodes = convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }, element) + val firstParagraphContents = nodes.takeWhile { it !is ContentParagraph } + val firstParagraph = ContentParagraph() + if (firstParagraphContents.isNotEmpty()) { + firstParagraphContents.forEach { firstParagraph.append(it) } + result.append(firstParagraph) + } + + result.appendAll(nodes.drop(firstParagraphContents.size)) + + if (element is PsiMethod) { + val tagsByName = element.searchInheritedTags() + for ((tagName, tags) in tagsByName) { + for ((tag, context) in tags) { + val section = result.addSection(javadocSectionDisplayName(tagName), tag.getSubjectName()) + val signature = signatureProvider.signature(element) + when (tagName) { + "param" -> { + section.appendTypeElement(signature) { + it.details + .find { node -> node.kind == NodeKind.Parameter && node.name == tag.getSubjectName() } + ?.detailOrNull(NodeKind.Type) + } + } + "return" -> { + section.appendTypeElement(signature) { it.detailOrNull(NodeKind.Type) } + } + } + section.appendAll(convertJavadocElements(tag.contentElements(), context)) + } + } + } + docComment.tags.forEach { tag -> - when(tag.name) { + when (tag.name) { "see" -> result.convertSeeTag(tag) "deprecated" -> { - deprecatedContent = Content() - deprecatedContent!!.convertJavadocElements(tag.contentElements()) + deprecatedContent = Content().apply { + appendAll(convertJavadocElements(tag.contentElements(), element)) + } } + in tagsToInherit -> {} else -> { val subjectName = tag.getSubjectName() val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName) - section.convertJavadocElements(tag.contentElements()) + section.appendAll(convertJavadocElements(tag.contentElements(), element)) } } } return JavadocParseResult(result, deprecatedContent) } + private val tagsToInherit = setOf("param", "return", "throws") + + private data class TagWithContext(val tag: PsiDocTag, val context: PsiNamedElement) + + private fun PsiMethod.searchInheritedTags(): Map<String, Collection<TagWithContext>> { + + val output = tagsToInherit.keysToMap { mutableMapOf<String?, TagWithContext>() } + + fun recursiveSearch(methods: Array<PsiMethod>) { + for (method in methods) { + recursiveSearch(method.findSuperMethods()) + } + for (method in methods) { + for (tag in method.docComment?.tags.orEmpty()) { + if (tag.name in tagsToInherit) { + output[tag.name]!![tag.getSubjectName()] = TagWithContext(tag, method) + } + } + } + } + + recursiveSearch(arrayOf(this)) + return output.mapValues { it.value.values } + } + + private fun PsiDocTag.contentElements(): Iterable<PsiElement> { val tagValueElements = children - .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME } - .dropWhile { it is PsiWhiteSpace } - .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS } + .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME } + .dropWhile { it is PsiWhiteSpace } + .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS } return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements } - private fun ContentBlock.convertJavadocElements(elements: Iterable<PsiElement>) { + private fun convertJavadocElements(elements: Iterable<PsiElement>, element: PsiNamedElement): List<ContentNode> { + val doc = Jsoup.parse(expandAllForElements(elements, element)) + return doc.body().childNodes().mapNotNull { + convertHtmlNode(it) + } + } + + private fun ContentBlock.appendAll(nodes: List<ContentNode>) { + nodes.forEach { append(it) } + } + + private fun expandAllForElements(elements: Iterable<PsiElement>, element: PsiNamedElement): String { val htmlBuilder = StringBuilder() elements.forEach { if (it is PsiInlineDocTag) { - htmlBuilder.append(convertInlineDocTag(it)) + htmlBuilder.append(convertInlineDocTag(it, element)) } else { htmlBuilder.append(it.text) } } - val doc = Jsoup.parse(htmlBuilder.toString().trim()) - doc.body().childNodes().forEach { - convertHtmlNode(it) - } + return htmlBuilder.toString().trim() } - private fun ContentBlock.convertHtmlNode(node: Node) { + private fun convertHtmlNode(node: Node, insidePre: Boolean = false): ContentNode? { if (node is TextNode) { - append(ContentText(node.text())) + val text = if (insidePre) node.wholeText else node.text() + return ContentText(text) } else if (node is Element) { - val childBlock = createBlock(node) + val childBlock = createBlock(node, insidePre) + node.childNodes().forEach { - childBlock.convertHtmlNode(it) + val child = convertHtmlNode(it, insidePre || childBlock is ContentBlockCode) + if (child != null) { + childBlock.append(child) + } } - append(childBlock) + return childBlock } + return null } - private fun createBlock(element: Element): ContentBlock = when(element.tagName()) { + private fun createBlock(element: Element, insidePre: Boolean): ContentBlock = when (element.tagName()) { "p" -> ContentParagraph() "b", "strong" -> ContentStrong() "i", "em" -> ContentEmphasis() "s", "del" -> ContentStrikethrough() - "code" -> ContentCode() + "code" -> if (insidePre) ContentBlock() else ContentCode() "pre" -> ContentBlockCode() "ul" -> ContentUnorderedList() "ol" -> ContentOrderedList() @@ -99,45 +189,73 @@ class JavadocParser(private val refGraph: NodeReferenceGraph, } private fun createLink(element: Element): ContentBlock { - val docref = element.attr("docref") - if (docref != null) { - return ContentNodeLazyLink(docref, { -> refGraph.lookupOrWarn(docref, logger)}) - } - val href = element.attr("href") - if (href != null) { - return ContentExternalLink(href) - } else { - return ContentBlock() + return when { + element.hasAttr("docref") -> { + val docref = element.attr("docref") + ContentNodeLazyLink(docref, { -> refGraph.lookupOrWarn(docref, logger) }) + } + element.hasAttr("href") -> { + val href = element.attr("href") + + val uri = try { + URI(href) + } catch (_: Exception) { + null + } + + if (uri?.isAbsolute == false) { + ContentLocalLink(href) + } else { + ContentExternalLink(href) + } + } + element.hasAttr("name") -> { + ContentBookmark(element.attr("name")) + } + else -> ContentBlock() } } private fun MutableContent.convertSeeTag(tag: PsiDocTag) { - val linkElement = tag.linkElement() - if (linkElement == null) { - return - } + val linkElement = tag.linkElement() ?: return val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) - val linkSignature = resolveLink(linkElement) + + val valueElement = tag.referenceElement() + val externalLink = resolveExternalLink(valueElement) val text = ContentText(linkElement.text) - if (linkSignature != null) { - val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookupOrWarn(linkSignature, logger)}) - linkNode.append(text) - seeSection.append(linkNode) - } else { - seeSection.append(text) + + val linkSignature by lazy { resolveInternalLink(valueElement) } + val node = when { + externalLink != null -> { + val linkNode = ContentExternalLink(externalLink) + linkNode.append(text) + linkNode + } + linkSignature != null -> { + val linkNode = + ContentNodeLazyLink( + (tag.valueElement ?: linkElement).text, + { -> refGraph.lookupOrWarn(linkSignature, logger) } + ) + linkNode.append(text) + linkNode + } + else -> text } + seeSection.append(node) } - private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + private fun convertInlineDocTag(tag: PsiInlineDocTag, element: PsiNamedElement) = when (tag.name) { "link", "linkplain" -> { - val valueElement = tag.linkElement() - val linkSignature = resolveLink(valueElement) - if (linkSignature != null) { + val valueElement = tag.referenceElement() + val externalLink = resolveExternalLink(valueElement) + val linkSignature by lazy { resolveInternalLink(valueElement) } + if (externalLink != null || linkSignature != null) { val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text - val link = "<a docref=\"$linkSignature\">${labelText.htmlEscape()}</a>" + val linkTarget = if (externalLink != null) "href=\"$externalLink\"" else "docref=\"$linkSignature\"" + val link = "<a $linkTarget>${labelText.htmlEscape()}</a>" if (tag.name == "link") "<code>$link</code>" else link - } - else if (valueElement != null) { + } else if (valueElement != null) { valueElement.text } else { "" @@ -149,16 +267,45 @@ class JavadocParser(private val refGraph: NodeReferenceGraph, val escaped = text.toString().trimStart().htmlEscape() if (tag.name == "code") "<code>$escaped</code>" else escaped } + "inheritDoc" -> { + val result = (element as? PsiMethod)?.let { + // @{inheritDoc} is only allowed on functions + val parent = tag.parent + when (parent) { + is PsiDocComment -> element.findSuperDocCommentOrWarn() + is PsiDocTag -> element.findSuperDocTagOrWarn(parent) + else -> null + } + } + result ?: tag.text + } else -> tag.text } + private fun PsiDocTag.referenceElement(): PsiElement? = + linkElement()?.let { + if (it.node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) { + PsiTreeUtil.findChildOfType(it, PsiJavaCodeReferenceElement::class.java) + } else { + it + } + } + private fun PsiDocTag.linkElement(): PsiElement? = - valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } + valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } + + private fun resolveExternalLink(valueElement: PsiElement?): String? { + val target = valueElement?.reference?.resolve() + if (target != null) { + return externalDocumentationLinkResolver.buildExternalDocumentationLink(target) + } + return null + } - private fun resolveLink(valueElement: PsiElement?): String? { + private fun resolveInternalLink(valueElement: PsiElement?): String? { val target = valueElement?.reference?.resolve() if (target != null) { - return getSignature(target) + return signatureProvider.signature(target) } return null } @@ -169,4 +316,88 @@ class JavadocParser(private val refGraph: NodeReferenceGraph, } return null } + + private fun PsiMethod.findSuperDocCommentOrWarn(): String { + val method = findFirstSuperMethodWithDocumentation(this) + if (method != null) { + val descriptionElements = method.docComment?.descriptionElements?.dropWhile { + it.text.trim().isEmpty() + } ?: return "" + + return expandAllForElements(descriptionElements, method) + } + logger.warn("No docs found on supertype with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}") + return "" + } + + + private fun PsiMethod.findSuperDocTagOrWarn(elementToExpand: PsiDocTag): String { + val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, this) + + if (result != null) { + val (method, tag) = result + + val contentElements = tag.contentElements().dropWhile { it.text.trim().isEmpty() } + + val expandedString = expandAllForElements(contentElements, method) + + return expandedString + } + logger.warn("No docs found on supertype for @${elementToExpand.name} ${elementToExpand.getSubjectName()} with {@inheritDoc} method ${this.name} in ${this.containingFile.name}:${this.lineNumber()}") + return "" + } + + private fun findFirstSuperMethodWithDocumentation(current: PsiMethod): PsiMethod? { + val superMethods = current.findSuperMethods() + for (method in superMethods) { + val docs = method.docComment?.descriptionElements?.dropWhile { it.text.trim().isEmpty() } + if (!docs.isNullOrEmpty()) { + return method + } + } + for (method in superMethods) { + val result = findFirstSuperMethodWithDocumentation(method) + if (result != null) { + return result + } + } + + return null + } + + private fun findFirstSuperMethodWithDocumentationforTag(elementToExpand: PsiDocTag, current: PsiMethod): Pair<PsiMethod, PsiDocTag>? { + val superMethods = current.findSuperMethods() + val mappedFilteredTags = superMethods.map { + it to it.docComment?.tags?.filter { it.name == elementToExpand.name } + } + + for ((method, tags) in mappedFilteredTags) { + tags ?: continue + for (tag in tags) { + val (tagSubject, elementSubject) = when (tag.name) { + "throws" -> { + // match class names only for throws, ignore possibly fully qualified path + // TODO: Always match exactly here + tag.getSubjectName()?.split(".")?.last() to elementToExpand.getSubjectName()?.split(".")?.last() + } + else -> { + tag.getSubjectName() to elementToExpand.getSubjectName() + } + } + + if (tagSubject == elementSubject) { + return method to tag + } + } + } + + for (method in superMethods) { + val result = findFirstSuperMethodWithDocumentationforTag(elementToExpand, method) + if (result != null) { + return result + } + } + return null + } + } diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt index ffef399d..d73bef4a 100644 --- a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt +++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt @@ -1,13 +1,10 @@ package org.jetbrains.dokka import com.google.inject.Inject -import org.jetbrains.dokka.Model.DescriptorSignatureProvider import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi -import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi class DeclarationLinkResolver @Inject constructor(val resolutionFacade: DokkaResolutionFacade, @@ -15,7 +12,7 @@ class DeclarationLinkResolver val logger: DokkaLogger, val options: DocumentationOptions, val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver, - val descriptorSignatureProvider: DescriptorSignatureProvider) { + val elementSignatureProvider: ElementSignatureProvider) { fun tryResolveContentLink(fromDescriptor: DeclarationDescriptor, href: String): ContentBlock? { @@ -34,14 +31,14 @@ class DeclarationLinkResolver if (externalHref != null) { return ContentExternalLink(externalHref) } - val signature = descriptorSignatureProvider.signature(symbol) + val signature = elementSignatureProvider.signature(symbol) val referencedAt = fromDescriptor.signatureWithSourceLocation() return ContentNodeLazyLink(href, { -> val target = refGraph.lookup(signature) if (target == null) { - logger.warn("Can't find node by signature $signature, referenced at $referencedAt") + logger.warn("Can't find node by signature `$signature`, referenced at $referencedAt") } target }) diff --git a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt index 6e44df74..d1f98184 100644 --- a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt +++ b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt @@ -19,6 +19,8 @@ import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.annotations.argumentValue import org.jetbrains.kotlin.resolve.constants.StringValue @@ -33,12 +35,15 @@ class DescriptorDocumentationParser val linkResolver: DeclarationLinkResolver, val resolutionFacade: DokkaResolutionFacade, val refGraph: NodeReferenceGraph, - val sampleService: SampleProcessingService) + val sampleService: SampleProcessingService, + val signatureProvider: KotlinElementSignatureProvider, + val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver +) { - fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content = - parseDocumentationAndDetails(descriptor, inline).first + fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false, isDefaultNoArgConstructor: Boolean = false): Content = + parseDocumentationAndDetails(descriptor, inline, isDefaultNoArgConstructor).first - fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor, inline: Boolean = false): Pair<Content, (DocumentationNode) -> Unit> { + fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor, inline: Boolean = false, isDefaultNoArgConstructor: Boolean = false): Pair<Content, (DocumentationNode) -> Unit> { if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) { return parseJavadoc(descriptor) } @@ -59,7 +64,10 @@ class DescriptorDocumentationParser ?.resolveToDescriptorIfAny() ?: descriptor - var kdocText = kdoc.getContent() + var kdocText = if (isDefaultNoArgConstructor) { + getConstructorTagContent(descriptor) ?: kdoc.getContent() + } else kdoc.getContent() + // workaround for code fence parsing problem in IJ markdown parser if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) { kdocText += "\n" @@ -87,6 +95,13 @@ class DescriptorDocumentationParser return content to { node -> } } + private fun getConstructorTagContent(descriptor: DeclarationDescriptor): String? { + return ((DescriptorToSourceUtils.descriptorToDeclaration(descriptor)?.navigationElement as? KtElement) as KtDeclaration).docComment?.findSectionByTag( + KDocKnownTag.CONSTRUCTOR + )?.getContent() + } + + private fun DeclarationDescriptor.isSuppressWarning() : Boolean { val suppressAnnotation = annotations.findAnnotation(FqName(Suppress::class.qualifiedName!!)) return if (suppressAnnotation != null) { @@ -129,7 +144,12 @@ class DescriptorDocumentationParser fun parseJavadoc(descriptor: DeclarationDescriptor): Pair<Content, (DocumentationNode) -> Unit> { val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi if (psi is PsiDocCommentOwner) { - val parseResult = JavadocParser(refGraph, logger).parseDocumentation(psi as PsiNamedElement) + val parseResult = JavadocParser( + refGraph, + logger, + signatureProvider, + externalDocumentationLinkResolver + ).parseDocumentation(psi as PsiNamedElement) return parseResult.content to { node -> parseResult.deprecatedContent?.let { val deprecationNode = DocumentationNode("", it, NodeKind.Modifier) diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt index 7b50fff5..38804e39 100644 --- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt +++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt @@ -11,25 +11,29 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor import org.jetbrains.kotlin.idea.kdoc.findKDoc +import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType +import org.jetbrains.kotlin.idea.util.makeNotNullable +import org.jetbrains.kotlin.idea.util.toFuzzyType import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.addRemoveModifier.MODIFIERS_ORDER import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors -import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.* -import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.types.typeUtil.supertypes +import org.jetbrains.kotlin.util.supertypesWithAny import java.io.File import java.nio.file.Path import java.nio.file.Paths @@ -50,10 +54,12 @@ class DocumentationOptions(val outputDir: String, perPackageOptions: List<PackageOptions> = emptyList(), externalDocumentationLinks: List<ExternalDocumentationLink> = emptyList(), noStdlibLink: Boolean, + noJdkLink: Boolean = false, val languageVersion: String?, val apiVersion: String?, cacheRoot: String? = null, - val suppressedFiles: Set<File> = emptySet()) { + val suppressedFiles: Set<File> = emptySet(), + val collectInheritedExtensionsFromLibraries: Boolean = false) { init { if (perPackageOptions.any { it.prefix == "" }) throw IllegalArgumentException("Please do not register packageOptions with all match pattern, use global settings instead") @@ -66,7 +72,10 @@ class DocumentationOptions(val outputDir: String, fun effectivePackageOptions(pack: FqName): PackageOptions = effectivePackageOptions(pack.asString()) val defaultLinks = run { - val links = mutableListOf(ExternalDocumentationLink.Builder("http://docs.oracle.com/javase/$jdkVersion/docs/api/").build()) + val links = mutableListOf<ExternalDocumentationLink>() + if (!noJdkLink) + links += ExternalDocumentationLink.Builder("https://docs.oracle.com/javase/$jdkVersion/docs/api/").build() + if (!noStdlibLink) links += ExternalDocumentationLink.Builder("https://kotlinlang.org/api/latest/jvm/stdlib/").build() links @@ -103,6 +112,10 @@ interface DefaultPlatformsProvider { fun getDefaultPlatforms(descriptor: DeclarationDescriptor): List<String> } +val ignoredSupertypes = setOf( + "kotlin.Annotation", "kotlin.Enum", "kotlin.Any" +) + class DocumentationBuilder @Inject constructor(val resolutionFacade: DokkaResolutionFacade, val descriptorDocumentationParser: DescriptorDocumentationParser, @@ -134,8 +147,20 @@ class DocumentationBuilder refGraph.register(descriptor.signature(), node) } - fun <T> nodeForDescriptor(descriptor: T, kind: NodeKind): DocumentationNode where T : DeclarationDescriptor, T : Named { - val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor, kind == NodeKind.Parameter) + fun <T> nodeForDescriptor( + descriptor: T, + kind: NodeKind, + external: Boolean = false + ): DocumentationNode where T : DeclarationDescriptor, T : Named { + val (doc, callback) = + if (external) { + Content.Empty to { node -> } + } else { + descriptorDocumentationParser.parseDocumentationAndDetails( + descriptor, + kind == NodeKind.Parameter + ) + } val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor) node.appendSignature(descriptor) callback(node) @@ -169,27 +194,20 @@ class DocumentationBuilder appendTextNode(modifier, NodeKind.Modifier) } - fun DocumentationNode.appendSupertype(descriptor: ClassDescriptor, superType: KotlinType) { + fun DocumentationNode.appendSupertype(descriptor: ClassDescriptor, superType: KotlinType, backref: Boolean) { val unwrappedType = superType.unwrap() if (unwrappedType is AbbreviatedType) { - appendSupertype(descriptor, unwrappedType.abbreviation) - } else if (!ignoreSupertype(unwrappedType)) { + appendSupertype(descriptor, unwrappedType.abbreviation, backref) + } else { appendType(unwrappedType, NodeKind.Supertype) val superclass = unwrappedType.constructor.declarationDescriptor - link(superclass, descriptor, RefKind.Inheritor) + if (backref) { + link(superclass, descriptor, RefKind.Inheritor) + } link(descriptor, superclass, RefKind.Superclass) } } - private fun ignoreSupertype(superType: KotlinType): Boolean { - val superClass = superType.constructor.declarationDescriptor as? ClassDescriptor - if (superClass != null) { - val fqName = DescriptorUtils.getFqNameSafe(superClass).asString() - return fqName == "kotlin.Annotation" || fqName == "kotlin.Enum" || fqName == "kotlin.Any" - } - return false - } - fun DocumentationNode.appendProjection(projection: TypeProjection, kind: NodeKind = NodeKind.Type) { if (projection.isStarProjection) { appendTextNode("*", NodeKind.Type) @@ -227,19 +245,39 @@ class DocumentationBuilder if (prefix != "") { node.appendTextNode(prefix, NodeKind.Modifier) } - if (kotlinType.isMarkedNullable) { + if (kotlinType.isNullabilityFlexible()) { + node.appendTextNode("!", NodeKind.NullabilityModifier) + } else if (kotlinType.isMarkedNullable) { node.appendTextNode("?", NodeKind.NullabilityModifier) } if (classifierDescriptor != null) { - val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor) + val externalLink = + linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor) if (externalLink != null) { - node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) + if (classifierDescriptor !is TypeParameterDescriptor) { + val targetNode = + refGraph.lookup(classifierDescriptor.signature()) ?: classifierDescriptor.build(true) + node.append(targetNode, RefKind.ExternalType) + node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) + } } else { - link(node, classifierDescriptor, - if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link) + link( + node, classifierDescriptor, + if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link + ) + } + if (classifierDescriptor !is TypeParameterDescriptor) { + node.append( + DocumentationNode( + classifierDescriptor.fqNameUnsafe.asString(), + Content.Empty, + NodeKind.QualifiedName + ), RefKind.Detail + ) } } + append(node, RefKind.Detail) node.appendAnnotations(kotlinType) for (typeArgument in kotlinType.arguments) { @@ -273,6 +311,17 @@ class DocumentationBuilder } } + fun DocumentationNode.appendExternalLink(externalLink: String) { + append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) + } + + fun DocumentationNode.appendExternalLink(descriptor: DeclarationDescriptor) { + val target = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(descriptor) + if (target != null) { + appendExternalLink(target) + } + } + fun DocumentationNode.appendSinceKotlin(annotation: DocumentationNode) { val kotlinVersion = annotation .detail(NodeKind.Parameter) @@ -284,7 +333,11 @@ class DocumentationBuilder fun DocumentationNode.appendModifiers(descriptor: DeclarationDescriptor) { val psi = (descriptor as DeclarationDescriptorWithSource).source.getPsi() as? KtModifierListOwner ?: return - KtTokens.MODIFIER_KEYWORDS_ARRAY.filter { it !in knownModifiers }.forEach { + KtTokens.MODIFIER_KEYWORDS_ARRAY.filter { + it !in knownModifiers + }.sortedBy { + MODIFIERS_ORDER.indexOf(it) + }.forEach { if (psi.hasModifier(it)) { appendTextNode(it.value, NodeKind.Modifier) } @@ -414,20 +467,49 @@ class DocumentationBuilder if (options.skipEmptyPackages && declarations.none { it.isDocumented(options) }) continue logger.info(" package $packageName: ${declarations.count()} declarations") - val packageNode = findOrCreatePackageNode(packageName.asString(), packageContent, this@DocumentationBuilder.refGraph) + val packageNode = findOrCreatePackageNode(this, packageName.asString(), packageContent, this@DocumentationBuilder.refGraph) packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode, declarations, allFqNames) } - propagateExtensionFunctionsToSubclasses(fragments) } - private fun propagateExtensionFunctionsToSubclasses(fragments: Collection<PackageFragmentDescriptor>) { - val allDescriptors = fragments.flatMap { it.getMemberScope().getContributedDescriptors() } - val allClasses = allDescriptors.filterIsInstance<ClassDescriptor>() - val classHierarchy = buildClassHierarchy(allClasses) + fun propagateExtensionFunctionsToSubclasses( + fragments: Collection<PackageFragmentDescriptor>, + resolutionFacade: DokkaResolutionFacade + ) { + + val moduleDescriptor = resolutionFacade.moduleDescriptor + + // Wide-collect all view descriptors + val allPackageViewDescriptors = generateSequence(listOf(moduleDescriptor.getPackage(FqName.ROOT))) { packages -> + packages + .flatMap { pkg -> + moduleDescriptor.getSubPackagesOf(pkg.fqName) { true } + }.map { fqName -> + moduleDescriptor.getPackage(fqName) + }.takeUnless { it.isEmpty() } + }.flatten() + + val allDescriptors = + if (options.collectInheritedExtensionsFromLibraries) { + allPackageViewDescriptors.map { it.memberScope } + } else { + fragments.asSequence().map { it.getMemberScope() } + }.flatMap { + it.getDescriptorsFiltered( + DescriptorKindFilter.CALLABLES + ).asSequence() + } + - val allExtensionFunctions = allDescriptors + val documentingDescriptors = fragments.flatMap { it.getMemberScope().getContributedDescriptors() } + val documentingClasses = documentingDescriptors.filterIsInstance<ClassDescriptor>() + + val classHierarchy = buildClassHierarchy(documentingClasses) + + val allExtensionFunctions = + allDescriptors .filterIsInstance<CallableMemberDescriptor>() .filter { it.extensionReceiverParameter != null } val extensionFunctionsByName = allExtensionFunctions.groupBy { it.name } @@ -435,15 +517,31 @@ class DocumentationBuilder for (extensionFunction in allExtensionFunctions) { if (extensionFunction.dispatchReceiverParameter != null) continue val possiblyShadowingFunctions = extensionFunctionsByName[extensionFunction.name] - ?.filter { fn -> fn.canShadow(extensionFunction) } + ?.filter { fn -> fn.canShadow(extensionFunction) } ?: emptyList() if (extensionFunction.extensionReceiverParameter?.type?.isDynamic() == true) continue - val classDescriptor = extensionFunction.getExtensionClassDescriptor() ?: continue - val subclasses = classHierarchy[classDescriptor] ?: continue - subclasses.forEach { subclass -> + val subclasses = + classHierarchy.filter { (key) -> key.isExtensionApplicable(extensionFunction) } + if (subclasses.isEmpty()) continue + subclasses.values.flatten().forEach { subclass -> if (subclass.isExtensionApplicable(extensionFunction) && - possiblyShadowingFunctions.none { subclass.isExtensionApplicable(it) }) { + possiblyShadowingFunctions.none { subclass.isExtensionApplicable(it) }) { + + val hasExternalLink = + linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink( + extensionFunction + ) != null + if (hasExternalLink) { + val containerDesc = + extensionFunction.containingDeclaration as? PackageFragmentDescriptor + if (containerDesc != null) { + val container = refGraph.lookup(containerDesc.signature()) + ?: containerDesc.buildExternal() + container.append(extensionFunction.buildExternal(), RefKind.Member) + } + } + refGraph.link(subclass.signature(), extensionFunction.signature(), RefKind.Extension) } } @@ -451,12 +549,9 @@ class DocumentationBuilder } private fun ClassDescriptor.isExtensionApplicable(extensionFunction: CallableMemberDescriptor): Boolean { - val receiverType = extensionFunction.extensionReceiverParameter!!.type - if (receiverType.arguments.any { it.type.constructor.declarationDescriptor is TypeParameterDescriptor }) { - val receiverClass = receiverType.constructor.declarationDescriptor - return receiverClass is ClassDescriptor && DescriptorUtils.isSubclass(this, receiverClass) - } - return defaultType.isSubtypeOf(receiverType) + val receiverType = extensionFunction.fuzzyExtensionReceiverType()?.makeNotNullable() + val classType = defaultType.toFuzzyType(declaredTypeParameters) + return receiverType != null && classType.checkIsSubtypeOf(receiverType) != null } private fun buildClassHierarchy(classes: List<ClassDescriptor>): Map<ClassDescriptor, List<ClassDescriptor>> { @@ -495,34 +590,60 @@ class DocumentationBuilder } fun DeclarationDescriptor.build(): DocumentationNode = when (this) { - is ClassDescriptor -> build() + is ClassifierDescriptor -> build() is ConstructorDescriptor -> build() is PropertyDescriptor -> build() is FunctionDescriptor -> build() - is TypeParameterDescriptor -> build() is ValueParameterDescriptor -> build() is ReceiverParameterDescriptor -> build() - is TypeAliasDescriptor -> build() else -> throw IllegalStateException("Descriptor $this is not known") } - fun TypeAliasDescriptor.build(): DocumentationNode { + fun PackageFragmentDescriptor.buildExternal(): DocumentationNode { + val node = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.Package) + + val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(this) + if (externalLink != null) { + node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) + } + register(this, node) + return node + } + + fun CallableDescriptor.buildExternal(): DocumentationNode = when(this) { + is FunctionDescriptor -> build(true) + is PropertyDescriptor -> build(true) + else -> throw IllegalStateException("Descriptor $this is not known") + } + + + fun ClassifierDescriptor.build(external: Boolean = false): DocumentationNode = when (this) { + is ClassDescriptor -> build(external) + is TypeAliasDescriptor -> build(external) + is TypeParameterDescriptor -> build() + else -> throw IllegalStateException("Descriptor $this is not known") + } + + fun TypeAliasDescriptor.build(external: Boolean = false): DocumentationNode { val node = nodeForDescriptor(this, NodeKind.TypeAlias) - node.appendAnnotations(this) + if (!external) { + node.appendAnnotations(this) + } node.appendModifiers(this) node.appendInPageChildren(typeConstructor.parameters, RefKind.Detail) node.appendType(underlyingType, NodeKind.TypeAliasUnderlyingType) - node.appendSourceLink(source) - node.appendDefaultPlatforms(this) - + if (!external) { + node.appendSourceLink(source) + node.appendDefaultPlatforms(this) + } register(this, node) return node } - fun ClassDescriptor.build(): DocumentationNode { + fun ClassDescriptor.build(external: Boolean = false): DocumentationNode { val kind = when { kind == ClassKind.OBJECT -> NodeKind.Object kind == ClassKind.INTERFACE -> NodeKind.Interface @@ -532,21 +653,25 @@ class DocumentationBuilder isSubclassOfThrowable() -> NodeKind.Exception else -> NodeKind.Class } - val node = nodeForDescriptor(this, kind) - typeConstructor.supertypes.forEach { - node.appendSupertype(this, it) + val node = nodeForDescriptor(this, kind, external) + register(this, node) + supertypesWithAnyPrecise().forEach { + node.appendSupertype(this, it, !external) } if (getKind() != ClassKind.OBJECT && getKind() != ClassKind.ENUM_ENTRY) { node.appendInPageChildren(typeConstructor.parameters, RefKind.Detail) } - for ((descriptor, inheritedLinkKind, extraModifier) in collectMembersToDocument()) { - node.appendClassMember(descriptor, inheritedLinkKind, extraModifier) + if (!external) { + for ((descriptor, inheritedLinkKind, extraModifier) in collectMembersToDocument()) { + node.appendClassMember(descriptor, inheritedLinkKind, extraModifier) + } + node.appendAnnotations(this) } - node.appendAnnotations(this) node.appendModifiers(this) - node.appendSourceLink(source) - node.appendDefaultPlatforms(this) - register(this, node) + if (!external) { + node.appendSourceLink(source) + node.appendDefaultPlatforms(this) + } return node } @@ -613,12 +738,12 @@ class DocumentationBuilder return (receiver?.type?.constructor?.declarationDescriptor as? ClassDescriptor)?.isCompanionObject ?: false } - fun FunctionDescriptor.build(): DocumentationNode { + fun FunctionDescriptor.build(external: Boolean = false): DocumentationNode { if (ErrorUtils.containsErrorType(this)) { logger.warn("Found an unresolved type in ${signatureWithSourceLocation()}") } - val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectFunction else NodeKind.Function) + val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectFunction else NodeKind.Function, external) node.appendInPageChildren(typeParameters, RefKind.Detail) extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) } @@ -626,8 +751,12 @@ class DocumentationBuilder node.appendType(returnType) node.appendAnnotations(this) node.appendModifiers(this) - node.appendSourceLink(source) - node.appendDefaultPlatforms(this) + if (!external) { + node.appendSourceLink(source) + node.appendDefaultPlatforms(this) + } else { + node.appendExternalLink(this) + } overriddenDescriptors.forEach { addOverrideLink(it, this) @@ -648,32 +777,47 @@ class DocumentationBuilder } } - fun PropertyDescriptor.build(): DocumentationNode { - val node = nodeForDescriptor(this, if (inCompanionObject()) NodeKind.CompanionObjectProperty else NodeKind.Property) + fun PropertyDescriptor.build(external: Boolean = false): DocumentationNode { + val node = nodeForDescriptor( + this, + if (inCompanionObject()) NodeKind.CompanionObjectProperty else NodeKind.Property, + external + ) node.appendInPageChildren(typeParameters, RefKind.Detail) extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) } node.appendType(returnType) node.appendAnnotations(this) node.appendModifiers(this) - node.appendSourceLink(source) - if (isVar) { - node.appendTextNode("var", NodeKind.Modifier) - } - getter?.let { - if (!it.isDefault) { - node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter") + if (!external) { + node.appendSourceLink(source) + if (isVar) { + node.appendTextNode("var", NodeKind.Modifier) } - } - setter?.let { - if (!it.isDefault) { - node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter") + + if (isConst) { + this.compileTimeInitializer?.toDocumentationNode()?.let { node.append(it, RefKind.Detail) } + } + + + getter?.let { + if (!it.isDefault) { + node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter") + } + } + setter?.let { + if (!it.isDefault) { + node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter") + } } + node.appendDefaultPlatforms(this) + } + if (external) { + node.appendExternalLink(this) } overriddenDescriptors.forEach { addOverrideLink(it, this) } - node.appendDefaultPlatforms(this) register(this, node) return node @@ -775,8 +919,8 @@ class DocumentationBuilder return node } - fun ConstantValue<*>.toDocumentationNode(): DocumentationNode? = value?.let { value -> - when (value) { + fun ConstantValue<*>.toDocumentationNode(): DocumentationNode? = value.let { value -> + val text = when (value) { is String -> "\"" + StringUtil.escapeStringCharacters(value) + "\"" is EnumEntrySyntheticClassDescriptor -> @@ -789,19 +933,40 @@ class DocumentationBuilder value.toString() } } - else -> value.toString() - }.let { valueString -> - DocumentationNode(valueString, Content.Empty, NodeKind.Value) + else -> "$value" } + DocumentationNode(text, Content.Empty, NodeKind.Value) + } + + + fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor, + externalClassNodes: MutableMap<FqName, DocumentationNode>, + allFqNames: Collection<FqName>): DocumentationNode { + if (descriptor is CallableMemberDescriptor) { + val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() + if (extensionClassDescriptor != null && isExtensionForExternalClass(descriptor, extensionClassDescriptor, allFqNames) && + !ErrorUtils.isError(extensionClassDescriptor)) { + val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor) + return externalClassNodes.getOrPut(fqName, { + val newNode = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.ExternalClass) + val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(extensionClassDescriptor) + if (externalLink != null) { + newNode.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) + } + append(newNode, RefKind.Member) + newNode + }) + } + } + return this } -} -val visibleToDocumentation = setOf(Visibilities.PROTECTED, Visibilities.PUBLIC) +} fun DeclarationDescriptor.isDocumented(options: DocumentationOptions): Boolean { return (options.effectivePackageOptions(fqNameSafe).includeNonPublic || this !is MemberDescriptor - || this.visibility in visibleToDocumentation) + || this.visibility.isPublicAPI) && !isDocumentationSuppressed(options) && (!options.effectivePackageOptions(fqNameSafe).skipDeprecated || !isDeprecated()) } @@ -833,16 +998,11 @@ class KotlinJavaDocumentationBuilder val logger: DokkaLogger) : JavaDocumentationBuilder { override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) { val classDescriptors = file.classes.map { - val javaDescriptorResolver = resolutionFacade.getFrontendService(JavaDescriptorResolver::class.java) - - javaDescriptorResolver.resolveClass(JavaClassImpl(it)) ?: run { - logger.warn("Cannot find descriptor for Java class ${it.qualifiedName}") - null - } + it.getJavaClassDescriptor(resolutionFacade) } if (classDescriptors.any { it != null && it.isDocumented(options) }) { - val packageNode = module.findOrCreatePackageNode(file.packageName, packageContent, documentationBuilder.refGraph) + val packageNode = findOrCreatePackageNode(module, file.packageName, packageContent, documentationBuilder.refGraph) for (descriptor in classDescriptors.filterNotNull()) { with(documentationBuilder) { @@ -893,24 +1053,6 @@ fun DeclarationDescriptor.isDeprecated(): Boolean = annotations.any { DescriptorUtils.getFqName(it.type.constructor.declarationDescriptor!!).asString() == "kotlin.Deprecated" } || (this is ConstructorDescriptor && containingDeclaration.isDeprecated()) -fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor, - externalClassNodes: MutableMap<FqName, DocumentationNode>, - allFqNames: Collection<FqName>): DocumentationNode { - if (descriptor is CallableMemberDescriptor) { - val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() - if (extensionClassDescriptor != null && isExtensionForExternalClass(descriptor, extensionClassDescriptor, allFqNames) && - !ErrorUtils.isError(extensionClassDescriptor)) { - val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor) - return externalClassNodes.getOrPut(fqName, { - val newNode = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.ExternalClass) - append(newNode, RefKind.Member) - newNode - }) - } - } - return this -} - fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? { val extensionReceiver = extensionReceiverParameter if (extensionReceiver != null) { @@ -1004,7 +1146,7 @@ fun DocumentationModule.prepareForGeneration(options: DocumentationOptions) { fun DocumentationNode.generateAllTypesNode() { val allTypes = members(NodeKind.Package) .flatMap { it.members.filter { it.kind in NodeKind.classLike || it.kind == NodeKind.ExternalClass } } - .sortedBy { if (it.kind == NodeKind.ExternalClass) it.name.substringAfterLast('.') else it.name } + .sortedBy { if (it.kind == NodeKind.ExternalClass) it.name.substringAfterLast('.').toLowerCase() else it.name.toLowerCase() } val allTypesNode = DocumentationNode("alltypes", Content.Empty, NodeKind.AllTypes) for (typeNode in allTypes) { @@ -1013,3 +1155,10 @@ fun DocumentationNode.generateAllTypesNode() { append(allTypesNode, RefKind.Member) } + +fun ClassDescriptor.supertypesWithAnyPrecise(): Collection<KotlinType> { + if (KotlinBuiltIns.isAny(this)) { + return emptyList() + } + return typeConstructor.supertypesWithAny() +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt index e19ecf76..a8129793 100644 --- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt +++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt @@ -2,14 +2,16 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.google.inject.Singleton +import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.util.io.* +import org.jetbrains.dokka.Formats.FileGeneratorBasedFormatDescriptor +import org.jetbrains.dokka.Formats.FormatDescriptor +import org.jetbrains.dokka.Utilities.ServiceLocator +import org.jetbrains.dokka.Utilities.lookup import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor -import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor -import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor -import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor -import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor +import org.jetbrains.kotlin.load.java.descriptors.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe @@ -21,12 +23,15 @@ import java.net.URL import java.net.URLConnection import java.nio.file.Path import java.security.MessageDigest +import javax.inject.Named +import kotlin.reflect.full.findAnnotation fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) } @Singleton class ExternalDocumentationLinkResolver @Inject constructor( val options: DocumentationOptions, + @Named("libraryResolutionFacade") val libraryResolutionFacade: DokkaResolutionFacade, val logger: DokkaLogger ) { @@ -129,13 +134,22 @@ class ExternalDocumentationLinkResolver @Inject constructor( .map { (key, value) -> key to value } .toMap() - val resolver = if (format == "javadoc") { - InboundExternalLinkResolutionService.Javadoc() - } else { - val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?: - throw RuntimeException("Failed to parse package list from $packageListUrl") - InboundExternalLinkResolutionService.Dokka(linkExtension) - } + + val defaultResolverDesc = services["dokka-default"]!! + val resolverDesc = services[format] + ?: defaultResolverDesc.takeIf { format in formatsWithDefaultResolver } + ?: defaultResolverDesc.also { + logger.warn("Couldn't find InboundExternalLinkResolutionService(format = `$format`) for $link, using Dokka default") + } + + + val resolverClass = javaClass.classLoader.loadClass(resolverDesc.className).kotlin + + val constructors = resolverClass.constructors + + val constructor = constructors.singleOrNull() + ?: constructors.first { it.findAnnotation<Inject>() != null } + val resolver = constructor.call(paramsMap) as InboundExternalLinkResolutionService val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations) @@ -152,11 +166,17 @@ class ExternalDocumentationLinkResolver @Inject constructor( } } + fun buildExternalDocumentationLink(element: PsiElement): String? { + return element.extractDescriptor(libraryResolutionFacade)?.let { + buildExternalDocumentationLink(it) + } + } + fun buildExternalDocumentationLink(symbol: DeclarationDescriptor): String? { val packageFqName: FqName = when (symbol) { - is DeclarationDescriptorNonRoot -> symbol.parents.firstOrNull { it is PackageFragmentDescriptor }?.fqNameSafe ?: return null is PackageFragmentDescriptor -> symbol.fqName + is DeclarationDescriptorNonRoot -> symbol.parents.firstOrNull { it is PackageFragmentDescriptor }?.fqNameSafe ?: return null else -> return null } @@ -170,6 +190,15 @@ class ExternalDocumentationLinkResolver @Inject constructor( companion object { const val DOKKA_PARAM_PREFIX = "\$dokka." + val services = ServiceLocator.allServices("inbound-link-resolver").associateBy { it.name } + private val formatsWithDefaultResolver = + ServiceLocator + .allServices("format") + .filter { + val desc = ServiceLocator.lookup<FormatDescriptor>(it) as? FileGeneratorBasedFormatDescriptor + desc?.generatorServiceClass == FileGenerator::class + }.map { it.name } + .toSet() } } @@ -177,7 +206,7 @@ class ExternalDocumentationLinkResolver @Inject constructor( interface InboundExternalLinkResolutionService { fun getPath(symbol: DeclarationDescriptor): String? - class Javadoc : InboundExternalLinkResolutionService { + class Javadoc(paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService { override fun getPath(symbol: DeclarationDescriptor): String? { if (symbol is EnumEntrySyntheticClassDescriptor) { return getPath(symbol.containingDeclaration)?.let { it + "#" + symbol.name.asString() } @@ -187,7 +216,7 @@ interface InboundExternalLinkResolutionService { val containingClass = symbol.containingDeclaration as? JavaClassDescriptor ?: return null val containingClassLink = getPath(containingClass) if (containingClassLink != null) { - if (symbol is JavaMethodDescriptor) { + if (symbol is JavaMethodDescriptor || symbol is JavaClassConstructorDescriptor) { val psi = symbol.sourcePsi() as? PsiMethod if (psi != null) { val params = psi.parameterList.parameters.joinToString { it.type.canonicalText } @@ -203,7 +232,9 @@ interface InboundExternalLinkResolutionService { } } - class Dokka(val extension: String) : InboundExternalLinkResolutionService { + class Dokka(val paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService { + val extension = paramsMap["linkExtension"]?.singleOrNull() ?: error("linkExtension not provided for Dokka resolver") + override fun getPath(symbol: DeclarationDescriptor): String? { val leafElement = when (symbol) { is CallableDescriptor, is TypeAliasDescriptor -> true diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt deleted file mode 100644 index a3be658e..00000000 --- a/core/src/main/kotlin/Kotlin/KotlinAsJavaDescriptorSignatureProvider.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.jetbrains.dokka.Kotlin - -import org.jetbrains.dokka.Model.DescriptorSignatureProvider -import org.jetbrains.dokka.getSignature -import org.jetbrains.dokka.sourcePsi -import org.jetbrains.kotlin.asJava.toLightElements -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.psi.KtElement - -class KotlinAsJavaDescriptorSignatureProvider : DescriptorSignatureProvider { - override fun signature(forDesc: DeclarationDescriptor): String { - val sourcePsi = forDesc.sourcePsi() - val javaLikePsi = if (sourcePsi is KtElement) { - sourcePsi.toLightElements().firstOrNull() - } else { - sourcePsi - } - - return getSignature(javaLikePsi) ?: - "not implemented for $forDesc with psi: $sourcePsi" - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt index c7ed8292..00a795d0 100644 --- a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt +++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt @@ -6,9 +6,11 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiNamedElement import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtPropertyAccessor @@ -59,8 +61,9 @@ class KotlinAsJavaDocumentationParser return JavadocParseResult.Empty } } + val isDefaultNoArgConstructor = kotlinLightElement is KtLightMethod && origin is KtClass val descriptor = resolutionFacade.resolveToDescriptor(origin) - val content = descriptorDocumentationParser.parseDocumentation(descriptor, origin is KtParameter) + val content = descriptorDocumentationParser.parseDocumentation(descriptor, origin is KtParameter, isDefaultNoArgConstructor) return JavadocParseResult(content, null) } } diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt new file mode 100644 index 00000000..20ea179e --- /dev/null +++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaElementSignatureProvider.kt @@ -0,0 +1,25 @@ +package org.jetbrains.dokka + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.asJava.toLightElements +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.psi.KtElement + +class KotlinAsJavaElementSignatureProvider : ElementSignatureProvider { + + private fun PsiElement.javaLikePsi() = when { + this is KtElement -> toLightElements().firstOrNull() + else -> this + } + + override fun signature(forPsi: PsiElement): String { + return getSignature(forPsi.javaLikePsi()) ?: + "not implemented for $forPsi" + } + + override fun signature(forDesc: DeclarationDescriptor): String { + val sourcePsi = forDesc.sourcePsi() + return getSignature(sourcePsi?.javaLikePsi()) ?: + "not implemented for $forDesc with psi: $sourcePsi" + } +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt deleted file mode 100644 index 7ecd0389..00000000 --- a/core/src/main/kotlin/Kotlin/KotlinDescriptorSignatureProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.jetbrains.dokka.Kotlin - -import org.jetbrains.dokka.Model.DescriptorSignatureProvider -import org.jetbrains.dokka.signature -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor - -class KotlinDescriptorSignatureProvider : DescriptorSignatureProvider { - override fun signature(forDesc: DeclarationDescriptor): String = forDesc.signature() -}
\ No newline at end of file diff --git a/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt new file mode 100644 index 00000000..bcac0182 --- /dev/null +++ b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt @@ -0,0 +1,34 @@ +package org.jetbrains.dokka + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiPackage +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.BindingContext +import javax.inject.Inject + +class KotlinElementSignatureProvider @Inject constructor( + val resolutionFacade: DokkaResolutionFacade +) : ElementSignatureProvider { + override fun signature(forPsi: PsiElement): String { + return forPsi.extractDescriptor(resolutionFacade) + ?.let { signature(it) } + ?: run { "no desc for $forPsi in ${(forPsi as? PsiMember)?.containingClass}" } + } + + override fun signature(forDesc: DeclarationDescriptor): String = forDesc.signature() +} + + +fun PsiElement.extractDescriptor(resolutionFacade: DokkaResolutionFacade): DeclarationDescriptor? { + val forPsi = this + + return when (forPsi) { + is KtLightElement<*, *> -> return (forPsi.kotlinOrigin!!).extractDescriptor(resolutionFacade) + is PsiPackage -> resolutionFacade.moduleDescriptor.getPackage(FqName(forPsi.qualifiedName)) + is PsiMember -> forPsi.getJavaOrKotlinMemberDescriptor(resolutionFacade) + else -> resolutionFacade.resolveSession.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, forPsi] + } +} diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt index f33c8c96..5f43c22e 100644 --- a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt +++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt @@ -5,8 +5,13 @@ import org.jetbrains.dokka.LanguageService.RenderMode /** * Implements [LanguageService] and provides rendering of symbols in Kotlin language */ -class KotlinLanguageService : LanguageService { - private val fullOnlyModifiers = setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified") +class KotlinLanguageService : CommonLanguageService() { + override fun showModifierInSummary(node: DocumentationNode): Boolean { + return node.name !in fullOnlyModifiers + } + + private val fullOnlyModifiers = + setOf("public", "protected", "private", "internal", "inline", "noinline", "crossinline", "reified") override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode { return content { @@ -17,11 +22,12 @@ class KotlinLanguageService : LanguageService { NodeKind.EnumItem, NodeKind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name) + NodeKind.Parameter -> renderParameter(node, renderMode) NodeKind.TypeParameter -> renderTypeParameter(node, renderMode) NodeKind.Type, NodeKind.UpperBound -> renderType(node, renderMode) - NodeKind.Modifier -> renderModifier(node) + NodeKind.Modifier -> renderModifier(this, node, renderMode) NodeKind.Constructor, NodeKind.Function, NodeKind.CompanionObjectFunction -> renderFunction(node, renderMode) @@ -32,12 +38,6 @@ class KotlinLanguageService : LanguageService { } } - override fun renderName(node: DocumentationNode): String { - return when (node.kind) { - NodeKind.Constructor -> node.owner!!.name - else -> node.name - } - } override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? { if (nodes.size < 2) return null @@ -46,10 +46,17 @@ class KotlinLanguageService : LanguageService { return content { val typeParameter = functionWithTypeParameter.details(NodeKind.TypeParameter).first() if (functionWithTypeParameter.kind == NodeKind.Function) { - renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name)) - } - else { - renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name)) + renderFunction( + functionWithTypeParameter, + RenderMode.SUMMARY, + SummarizingMapper(receiverKind, typeParameter.name) + ) + } else { + renderProperty( + functionWithTypeParameter, + RenderMode.SUMMARY, + SummarizingMapper(receiverKind, typeParameter.name) + ) } } } @@ -70,26 +77,27 @@ class KotlinLanguageService : LanguageService { companion object { private val arrayClasses = setOf( - "kotlin.Array", - "kotlin.BooleanArray", - "kotlin.ByteArray", - "kotlin.CharArray", - "kotlin.ShortArray", - "kotlin.IntArray", - "kotlin.LongArray", - "kotlin.FloatArray", - "kotlin.DoubleArray" + "kotlin.Array", + "kotlin.BooleanArray", + "kotlin.ByteArray", + "kotlin.CharArray", + "kotlin.ShortArray", + "kotlin.IntArray", + "kotlin.LongArray", + "kotlin.FloatArray", + "kotlin.DoubleArray" ) private val arrayOrListClasses = setOf("kotlin.List") + arrayClasses private val iterableClasses = setOf( - "kotlin.Collection", - "kotlin.Sequence", - "kotlin.Iterable", - "kotlin.Map", - "kotlin.String", - "kotlin.CharSequence") + arrayOrListClasses + "kotlin.Collection", + "kotlin.Sequence", + "kotlin.Iterable", + "kotlin.Map", + "kotlin.String", + "kotlin.CharSequence" + ) + arrayOrListClasses } private enum class ReceiverKind(val receiverName: String, val classes: Collection<String>) { @@ -102,53 +110,21 @@ class KotlinLanguageService : LanguageService { fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) } - private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String): SignatureMapper { + private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String) : SignatureMapper { override fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) { to.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName)) to.text("<$typeParameterName>") } } - private fun ContentBlock.renderPackage(node: DocumentationNode) { - keyword("package") - text(" ") - identifier(node.name) - } - - private fun <T> ContentBlock.renderList(nodes: List<T>, separator: String = ", ", - noWrap: Boolean = false, renderItem: (T) -> Unit) { - if (nodes.none()) - return - renderItem(nodes.first()) - nodes.drop(1).forEach { - if (noWrap) { - symbol(separator.removeSuffix(" ")) - nbsp() - } else { - symbol(separator) - } - renderItem(it) - } - } - - private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode)->Unit) { - val to = node.links.firstOrNull() - if (to == null) - body(node) - else - link(to) { - body(node) - } - } - private fun ContentBlock.renderFunctionalTypeParameterName(node: DocumentationNode, renderMode: RenderMode) { node.references(RefKind.HiddenAnnotation).map { it.to } - .find { it.name == "ParameterName" }?.let { - val parameterNameValue = it.detail(NodeKind.Parameter).detail(NodeKind.Value) - identifier(parameterNameValue.name.removeSurrounding("\""), IdentifierKind.ParameterName) - symbol(":") - nbsp() - } + .find { it.name == "ParameterName" }?.let { + val parameterNameValue = it.detail(NodeKind.Parameter).detail(NodeKind.Value) + identifier(parameterNameValue.name.removeSurrounding("\""), IdentifierKind.ParameterName) + symbol(":") + nbsp() + } } private fun ContentBlock.renderFunctionalType(node: DocumentationNode, renderMode: RenderMode) { @@ -190,15 +166,27 @@ class KotlinLanguageService : LanguageService { keyword("dynamic") return } + + val nullabilityModifier = node.detailOrNull(NodeKind.NullabilityModifier) + if (node.isFunctionalType()) { - renderFunctionalType(node, renderMode) + if (nullabilityModifier != null) { + symbol("(") + renderFunctionalType(node, renderMode) + symbol(")") + symbol(nullabilityModifier.name) + } else { + renderFunctionalType(node, renderMode) + } return } if (renderMode == RenderMode.FULL) { renderAnnotationsForNode(node) } renderModifiersForNode(node, renderMode, true) - renderLinked(node) { identifier(it.name, IdentifierKind.TypeName) } + renderLinked(this, node) { + identifier(it.typeDeclarationClass?.classNodeNameWithOuterClass() ?: it.name, IdentifierKind.TypeName) + } val typeArguments = node.details(NodeKind.Type) if (typeArguments.isNotEmpty()) { symbol("<") @@ -207,22 +195,24 @@ class KotlinLanguageService : LanguageService { } symbol(">") } - val nullabilityModifier = node.details(NodeKind.NullabilityModifier).singleOrNull() - if (nullabilityModifier != null) { + + nullabilityModifier ?.apply { symbol(nullabilityModifier.name) } } - private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) { + override fun renderModifier( + block: ContentBlock, + node: DocumentationNode, + renderMode: RenderMode, + nowrap: Boolean + ) { when (node.name) { - "final", "public", "var" -> {} + "final", "public", "var" -> { + } else -> { - keyword(node.name) - if (nowrap) { - nbsp() - } - else { - text(" ") + if (showModifierInSummary(node) || renderMode == RenderMode.FULL) { + super.renderModifier(block, node, renderMode, nowrap) } } } @@ -238,11 +228,12 @@ class KotlinLanguageService : LanguageService { nbsp() symbol(":") nbsp() - renderList(constraints, noWrap=true) { + renderList(constraints, noWrap = true) { renderType(it, renderMode) } } } + private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) { if (renderMode == RenderMode.FULL) { renderAnnotationsForNode(node) @@ -274,9 +265,12 @@ class KotlinLanguageService : LanguageService { } private fun ContentBlock.renderExtraTypeParameterConstraints(node: DocumentationNode, renderMode: RenderMode) { - val parametersWithMultipleConstraints = node.details(NodeKind.TypeParameter).filter { it.details(NodeKind.UpperBound).size > 1 } + val parametersWithMultipleConstraints = + node.details(NodeKind.TypeParameter).filter { it.details(NodeKind.UpperBound).size > 1 } val parametersWithConstraints = parametersWithMultipleConstraints - .flatMap { parameter -> parameter.details(NodeKind.UpperBound).map { constraint -> parameter to constraint } } + .flatMap { parameter -> + parameter.details(NodeKind.UpperBound).map { constraint -> parameter to constraint } + } if (parametersWithMultipleConstraints.isNotEmpty()) { keyword(" where ") renderList(parametersWithConstraints) { @@ -290,7 +284,7 @@ class KotlinLanguageService : LanguageService { } private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) { - val supertypes = node.details(NodeKind.Supertype) + val supertypes = node.details(NodeKind.Supertype).filterNot { it.qualifiedNameFromType() in ignoredSupertypes } if (supertypes.any()) { nbsp() symbol(":") @@ -302,20 +296,6 @@ class KotlinLanguageService : LanguageService { } } - private fun ContentBlock.renderModifiersForNode(node: DocumentationNode, - renderMode: RenderMode, - nowrap: Boolean = false) { - val modifiers = node.details(NodeKind.Modifier) - for (it in modifiers) { - if (node.kind == org.jetbrains.dokka.NodeKind.Interface && it.name == "abstract") - continue - if (renderMode == RenderMode.SUMMARY && it.name in fullOnlyModifiers) { - continue - } - renderModifier(it, nowrap) - } - } - private fun ContentBlock.renderAnnotationsForNode(node: DocumentationNode) { node.annotations.forEach { renderAnnotation(it) @@ -365,9 +345,11 @@ class KotlinLanguageService : LanguageService { } } - private fun ContentBlock.renderFunction(node: DocumentationNode, - renderMode: RenderMode, - signatureMapper: SignatureMapper? = null) { + private fun ContentBlock.renderFunction( + node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? = null + ) { if (renderMode == RenderMode.FULL) { renderAnnotationsForNode(node) } @@ -385,7 +367,7 @@ class KotlinLanguageService : LanguageService { renderReceiver(node, renderMode, signatureMapper) - if (node.kind != org.jetbrains.dokka.NodeKind.Constructor) + if (node.kind != NodeKind.Constructor) identifierOrDeprecated(node) symbol("(") @@ -401,14 +383,17 @@ class KotlinLanguageService : LanguageService { symbol(")") symbol(": ") renderType(node.detail(NodeKind.Type), renderMode) - } - else { + } else { symbol(")") } renderExtraTypeParameterConstraints(node, renderMode) } - private fun ContentBlock.renderReceiver(node: DocumentationNode, renderMode: RenderMode, signatureMapper: SignatureMapper?) { + private fun ContentBlock.renderReceiver( + node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? + ) { val receiver = node.details(NodeKind.Receiver).singleOrNull() if (receiver != null) { if (signatureMapper != null) { @@ -428,17 +413,19 @@ class KotlinLanguageService : LanguageService { } } - private fun needReturnType(node: DocumentationNode) = when(node.kind) { + private fun needReturnType(node: DocumentationNode) = when (node.kind) { NodeKind.Constructor -> false else -> !node.isUnitReturnType() } fun DocumentationNode.isUnitReturnType(): Boolean = - detail(NodeKind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit" + detail(NodeKind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit" - private fun ContentBlock.renderProperty(node: DocumentationNode, - renderMode: RenderMode, - signatureMapper: SignatureMapper? = null) { + private fun ContentBlock.renderProperty( + node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? = null + ) { if (renderMode == RenderMode.FULL) { renderAnnotationsForNode(node) } @@ -462,7 +449,7 @@ class KotlinLanguageService : LanguageService { } fun DocumentationNode.getPropertyKeyword() = - if (details(NodeKind.Modifier).any { it.name == "var" }) "var" else "val" + if (details(NodeKind.Modifier).any { it.name == "var" }) "var" else "val" fun ContentBlock.identifierOrDeprecated(node: DocumentationNode) { if (node.deprecation != null) { @@ -475,4 +462,12 @@ class KotlinLanguageService : LanguageService { } } -fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name +fun DocumentationNode.qualifiedNameFromType(): String { + return details.firstOrNull { it.kind == NodeKind.QualifiedName }?.name + ?: (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() + ?: name +} + + +val DocumentationNode.typeDeclarationClass + get() = (links.firstOrNull { it.kind in NodeKind.classLike } ?: externalType) diff --git a/core/src/main/kotlin/Languages/CommonLanguageService.kt b/core/src/main/kotlin/Languages/CommonLanguageService.kt new file mode 100644 index 00000000..ddc95d32 --- /dev/null +++ b/core/src/main/kotlin/Languages/CommonLanguageService.kt @@ -0,0 +1,84 @@ +package org.jetbrains.dokka + + +abstract class CommonLanguageService : LanguageService { + + protected fun ContentBlock.renderPackage(node: DocumentationNode) { + keyword("package") + nbsp() + identifier(node.name) + } + + override fun renderName(node: DocumentationNode): String { + return when (node.kind) { + NodeKind.Constructor -> node.owner!!.name + else -> node.name + } + } + + open fun renderModifier( + block: ContentBlock, + node: DocumentationNode, + renderMode: LanguageService.RenderMode, + nowrap: Boolean = false + ) = with(block) { + keyword(node.name) + if (nowrap) { + nbsp() + } else { + text(" ") + } + } + + protected fun renderLinked( + block: ContentBlock, + node: DocumentationNode, + body: ContentBlock.(DocumentationNode) -> Unit + ) = with(block) { + val to = node.links.firstOrNull() + if (to == null) + body(node) + else + link(to) { + this.body(node) + } + } + + protected fun <T> ContentBlock.renderList( + nodes: List<T>, separator: String = ", ", + noWrap: Boolean = false, renderItem: (T) -> Unit + ) { + if (nodes.none()) + return + renderItem(nodes.first()) + nodes.drop(1).forEach { + if (noWrap) { + symbol(separator.removeSuffix(" ")) + nbsp() + } else { + symbol(separator) + } + renderItem(it) + } + } + + abstract fun showModifierInSummary(node: DocumentationNode): Boolean + + protected fun ContentBlock.renderModifiersForNode( + node: DocumentationNode, + renderMode: LanguageService.RenderMode, + nowrap: Boolean = false + ) { + val modifiers = node.details(NodeKind.Modifier) + for (it in modifiers) { + if (node.kind == NodeKind.Interface && it.name == "abstract") + continue + if (renderMode == LanguageService.RenderMode.SUMMARY && !showModifierInSummary(it)) { + continue + } + renderModifier(this, it, renderMode, nowrap) + } + } + + +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Languages/NewJavaLanguageService.kt b/core/src/main/kotlin/Languages/NewJavaLanguageService.kt new file mode 100644 index 00000000..992cd090 --- /dev/null +++ b/core/src/main/kotlin/Languages/NewJavaLanguageService.kt @@ -0,0 +1,197 @@ +package org.jetbrains.dokka + +import org.jetbrains.dokka.LanguageService.RenderMode + +/** + * Implements [LanguageService] and provides rendering of symbols in Java language + */ +class NewJavaLanguageService : CommonLanguageService() { + override fun showModifierInSummary(node: DocumentationNode): Boolean { + return true + } + + override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode { + return content { + (when (node.kind) { + NodeKind.Package -> renderPackage(node) + in NodeKind.classLike -> renderClass(node, renderMode) + + NodeKind.Modifier -> renderModifier(this, node, renderMode) + NodeKind.TypeParameter -> renderTypeParameter(node) + NodeKind.Type, + NodeKind.UpperBound -> renderType(node) + NodeKind.Parameter -> renderParameter(node) + NodeKind.Constructor, + NodeKind.Function -> renderFunction(node) + NodeKind.Property -> renderProperty(node) + else -> "${node.kind}: ${node.name}" + }) + } + } + + override fun summarizeSignatures(nodes: List<DocumentationNode>): ContentNode? = null + + + override fun renderModifier(block: ContentBlock, node: DocumentationNode, renderMode: RenderMode, nowrap: Boolean) { + when (node.name) { + "open", "internal" -> { + } + else -> super.renderModifier(block, node, renderMode, nowrap) + } + } + + fun getArrayElementType(node: DocumentationNode): DocumentationNode? = when (node.qualifiedName()) { + "kotlin.Array" -> + node.details(NodeKind.Type).singleOrNull()?.let { et -> getArrayElementType(et) ?: et } + ?: DocumentationNode("Object", node.content, NodeKind.ExternalClass) + + "kotlin.IntArray", "kotlin.LongArray", "kotlin.ShortArray", "kotlin.ByteArray", + "kotlin.CharArray", "kotlin.DoubleArray", "kotlin.FloatArray", "kotlin.BooleanArray" -> + DocumentationNode(node.name.removeSuffix("Array").toLowerCase(), node.content, NodeKind.Type) + + else -> null + } + + fun getArrayDimension(node: DocumentationNode): Int = when (node.qualifiedName()) { + "kotlin.Array" -> + 1 + (node.details(NodeKind.Type).singleOrNull()?.let { getArrayDimension(it) } ?: 0) + + "kotlin.IntArray", "kotlin.LongArray", "kotlin.ShortArray", "kotlin.ByteArray", + "kotlin.CharArray", "kotlin.DoubleArray", "kotlin.FloatArray", "kotlin.BooleanArray" -> + 1 + else -> 0 + } + + fun ContentBlock.renderType(node: DocumentationNode) { + when (node.name) { + "Unit" -> identifier("void") + "Int" -> identifier("int") + "Long" -> identifier("long") + "Double" -> identifier("double") + "Float" -> identifier("float") + "Char" -> identifier("char") + "Boolean" -> identifier("bool") + // TODO: render arrays + else -> renderLinked(this, node) { + identifier(node.name) + } + } + } + + private fun ContentBlock.renderTypeParameter(node: DocumentationNode) { + val constraints = node.details(NodeKind.UpperBound) + if (constraints.none()) + identifier(node.name) + else { + identifier(node.name) + text(" ") + keyword("extends") + text(" ") + constraints.forEach { renderType(node) } + } + } + + private fun ContentBlock.renderParameter(node: DocumentationNode) { + renderType(node.detail(NodeKind.Type)) + text(" ") + identifier(node.name) + } + + private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode) { + val typeParameters = node.details(NodeKind.TypeParameter) + if (typeParameters.any()) { + symbol("<") + renderList(typeParameters, noWrap = true) { + renderTypeParameter(it) + } + symbol(">") + text(" ") + } + } + +// private fun renderModifiersForNode(node: DocumentationNode): String { +// val modifiers = node.details(NodeKind.Modifier).map { renderModifier(it) }.filter { it != "" } +// if (modifiers.none()) +// return "" +// return modifiers.joinToString(" ", postfix = " ") +// } + + private fun ContentBlock.renderClassKind(node: DocumentationNode) { + when (node.kind) { + NodeKind.Interface -> { + keyword("interface") + } + NodeKind.EnumItem -> { + keyword("enum value") + } + NodeKind.Enum -> { + keyword("enum") + } + NodeKind.Class, NodeKind.Exception, NodeKind.Object -> { + keyword("class") + } + else -> throw IllegalArgumentException("Node $node is not a class-like object") + } + text(" ") + } + + private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) { + renderModifiersForNode(node, renderMode) + renderClassKind(node) + + identifier(node.name) + renderTypeParametersForNode(node) + } + + private fun ContentBlock.renderParameters(nodes: List<DocumentationNode>) { + renderList(nodes) { + renderParameter(it) + } + } + + private fun ContentBlock.renderFunction(node: DocumentationNode) { + when (node.kind) { + NodeKind.Constructor -> identifier(node.owner?.name ?: "") + NodeKind.Function -> { + renderTypeParametersForNode(node) + renderType(node.detail(NodeKind.Type)) + text(" ") + identifier(node.name) + + } + else -> throw IllegalArgumentException("Node $node is not a function-like object") + } + + val receiver = node.details(NodeKind.Receiver).singleOrNull() + symbol("(") + if (receiver != null) + renderParameters(listOf(receiver) + node.details(NodeKind.Parameter)) + else + renderParameters(node.details(NodeKind.Parameter)) + + symbol(")") + } + + private fun ContentBlock.renderProperty(node: DocumentationNode) { + + when (node.kind) { + NodeKind.Property -> { + keyword("val") + text(" ") + } + else -> throw IllegalArgumentException("Node $node is not a property") + } + renderTypeParametersForNode(node) + val receiver = node.details(NodeKind.Receiver).singleOrNull() + if (receiver != null) { + renderType(receiver.detail(NodeKind.Type)) + symbol(".") + } + + identifier(node.name) + symbol(":") + text(" ") + renderType(node.detail(NodeKind.Type)) + + } +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Model/Content.kt b/core/src/main/kotlin/Model/Content.kt index 1f5bbc83..c142f4a4 100644 --- a/core/src/main/kotlin/Model/Content.kt +++ b/core/src/main/kotlin/Model/Content.kt @@ -9,7 +9,7 @@ object ContentEmpty : ContentNode { } open class ContentBlock() : ContentNode { - val children = arrayListOf<ContentNode>() + open val children = arrayListOf<ContentNode>() fun append(node: ContentNode) { children.add(node) @@ -27,6 +27,34 @@ open class ContentBlock() : ContentNode { get() = children.sumBy { it.textLength } } +class NodeRenderContent( + val node: DocumentationNode, + val mode: LanguageService.RenderMode +): ContentNode { + override val textLength: Int + get() = 0 //TODO: Clarify? +} + +class LazyContentBlock(private val fillChildren: () -> List<ContentNode>) : ContentBlock() { + private var computed = false + override val children: ArrayList<ContentNode> + get() { + if (!computed) { + computed = true + children.addAll(fillChildren()) + } + return super.children + } + + override fun equals(other: Any?): Boolean { + return other is LazyContentBlock && other.fillChildren == fillChildren && super.equals(other) + } + + override fun hashCode(): Int { + return super.hashCode() + 31 * fillChildren.hashCode() + } +} + enum class IdentifierKind { TypeName, ParameterName, @@ -87,6 +115,7 @@ class ContentBlockSampleCode(language: String = "kotlin", val importsBlock: Cont abstract class ContentNodeLink() : ContentBlock() { abstract val node: DocumentationNode? + abstract val text: String? } object ContentHardLineBreak : ContentNode { @@ -100,6 +129,8 @@ class ContentNodeDirectLink(override val node: DocumentationNode): ContentNodeLi override fun hashCode(): Int = children.hashCode() * 31 + node.name.hashCode() + + override val text: String? = null } class ContentNodeLazyLink(val linkText: String, val lazyNode: () -> DocumentationNode?): ContentNodeLink() { @@ -110,6 +141,8 @@ class ContentNodeLazyLink(val linkText: String, val lazyNode: () -> Documentatio override fun hashCode(): Int = children.hashCode() * 31 + linkText.hashCode() + + override val text: String? = linkText } class ContentExternalLink(val href : String) : ContentBlock() { @@ -120,6 +153,9 @@ class ContentExternalLink(val href : String) : ContentBlock() { children.hashCode() * 31 + href.hashCode() } +data class ContentBookmark(val name: String): ContentBlock() +data class ContentLocalLink(val href: String) : ContentBlock() + class ContentUnorderedList() : ContentBlock() class ContentOrderedList() : ContentBlock() class ContentListItem() : ContentBlock() diff --git a/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt b/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt deleted file mode 100644 index 85584e3c..00000000 --- a/core/src/main/kotlin/Model/DescriptorSignatureProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.jetbrains.dokka.Model - -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor - -interface DescriptorSignatureProvider { - fun signature(forDesc: DeclarationDescriptor): String -}
\ No newline at end of file diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt index da85cabf..ad7801f2 100644 --- a/core/src/main/kotlin/Model/DocumentationNode.kt +++ b/core/src/main/kotlin/Model/DocumentationNode.kt @@ -48,6 +48,7 @@ enum class NodeKind { Signature, ExternalLink, + QualifiedName, Platform, AllTypes, @@ -62,7 +63,7 @@ enum class NodeKind { companion object { val classLike = setOf(Class, Interface, Enum, AnnotationClass, Exception, Object, TypeAlias) - val memberLike = setOf(Function, Property, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem) + val memberLike = setOf(Function, Property, Field, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem) } } @@ -85,6 +86,8 @@ open class DocumentationNode(val name: String, get() = references(RefKind.Member).map { it.to } val inheritedMembers: List<DocumentationNode> get() = references(RefKind.InheritedMember).map { it.to } + val allInheritedMembers: List<DocumentationNode> + get() = recursiveInheritedMembers() val inheritedCompanionObjectMembers: List<DocumentationNode> get() = references(RefKind.InheritedCompanionObjectMember).map { it.to } val extensions: List<DocumentationNode> @@ -103,6 +106,28 @@ open class DocumentationNode(val name: String, get() = references(RefKind.Deprecation).singleOrNull()?.to val platforms: List<String> get() = references(RefKind.Platform).map { it.to.name } + val externalType: DocumentationNode? + get() = references(RefKind.ExternalType).map { it.to }.firstOrNull() + + val supertypes: List<DocumentationNode> + get() = details(NodeKind.Supertype) + + val superclassType: DocumentationNode? + get() = when (kind) { + NodeKind.Supertype -> { + (links + listOfNotNull(externalType)).firstOrNull { it.kind in NodeKind.classLike }?.superclassType + } + NodeKind.Interface -> null + in NodeKind.classLike -> supertypes.firstOrNull { + (it.links + listOfNotNull(it.externalType)).any { it.isSuperclassFor(this) } + } + else -> null + } + + val superclassTypeSequence: Sequence<DocumentationNode> + get() = generateSequence(superclassType) { + it.superclassType + } // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice fun addReferenceTo(to: DocumentationNode, kind: RefKind) { @@ -123,7 +148,6 @@ open class DocumentationNode(val name: String, } (content as MutableContent).body() } - fun details(kind: NodeKind): List<DocumentationNode> = details.filter { it.kind == kind } fun members(kind: NodeKind): List<DocumentationNode> = members.filter { it.kind == kind } fun inheritedMembers(kind: NodeKind): List<DocumentationNode> = inheritedMembers.filter { it.kind == kind } @@ -154,17 +178,21 @@ val DocumentationNode.path: List<DocumentationNode> return parent.path + this } -fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode { - val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName } - if (existingNode != null) { - return existingNode - } - val newNode = DocumentationNode(packageName, +fun findOrCreatePackageNode(module: DocumentationNode?, packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode { + val node = refGraph.lookup(packageName) ?: run { + val newNode = DocumentationNode( + packageName, packageContent.getOrElse(packageName) { Content.Empty }, - NodeKind.Package) - append(newNode, RefKind.Member) - refGraph.register(packageName, newNode) - return newNode + NodeKind.Package + ) + + refGraph.register(packageName, newNode) + newNode + } + if (module != null && node !in module.members) { + module.append(node, RefKind.Member) + } + return node } fun DocumentationNode.append(child: DocumentationNode, kind: RefKind) { @@ -173,7 +201,8 @@ fun DocumentationNode.append(child: DocumentationNode, kind: RefKind) { RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner) RefKind.Member -> child.addReferenceTo(this, RefKind.Owner) RefKind.Owner -> child.addReferenceTo(this, RefKind.Member) - else -> { /* Do not add any links back for other types */ } + else -> { /* Do not add any links back for other types */ + } } } @@ -186,8 +215,37 @@ fun DocumentationNode.appendTextNode(text: String, fun DocumentationNode.qualifiedName(): String { if (kind == NodeKind.Type) { return qualifiedNameFromType() + } else if (kind == NodeKind.Package) { + return name } - return path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".") + return path.dropWhile { it.kind == NodeKind.Module }.map { it.name }.filter { it.isNotEmpty() }.joinToString(".") } fun DocumentationNode.simpleName() = name.substringAfterLast('.') + +private fun DocumentationNode.recursiveInheritedMembers(): List<DocumentationNode> { + val allInheritedMembers = mutableListOf<DocumentationNode>() + recursiveInheritedMembers(allInheritedMembers) + return allInheritedMembers +} + +private fun DocumentationNode.recursiveInheritedMembers(allInheritedMembers: MutableList<DocumentationNode>) { + allInheritedMembers.addAll(inheritedMembers) + System.out.println(allInheritedMembers.size) + inheritedMembers.groupBy { it.owner!! } .forEach { (node, _) -> + node.recursiveInheritedMembers(allInheritedMembers) + } +} + +private fun DocumentationNode.isSuperclassFor(node: DocumentationNode): Boolean { + return when(node.kind) { + NodeKind.Object, NodeKind.Class, NodeKind.Enum -> kind == NodeKind.Class + NodeKind.Exception -> kind == NodeKind.Class || kind == NodeKind.Exception + else -> false + } +} + +fun DocumentationNode.classNodeNameWithOuterClass(): String { + assert(kind in NodeKind.classLike) + return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name } +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Model/DocumentationReference.kt b/core/src/main/kotlin/Model/DocumentationReference.kt index a968f400..89ec1b3e 100644 --- a/core/src/main/kotlin/Model/DocumentationReference.kt +++ b/core/src/main/kotlin/Model/DocumentationReference.kt @@ -18,7 +18,8 @@ enum class RefKind { HiddenAnnotation, Deprecation, TopLevelPage, - Platform + Platform, + ExternalType } data class DocumentationReference(val from: DocumentationNode, val to: DocumentationNode, val kind: RefKind) { @@ -61,7 +62,7 @@ class NodeReferenceGraph() { fun lookupOrWarn(signature: String, logger: DokkaLogger): DocumentationNode? { val result = nodeMap[signature] if (result == null) { - logger.warn("Can't find node by signature $signature") + logger.warn("Can't find node by signature `$signature`") } return result } diff --git a/core/src/main/kotlin/Model/ElementSignatureProvider.kt b/core/src/main/kotlin/Model/ElementSignatureProvider.kt new file mode 100644 index 00000000..e8fdde6e --- /dev/null +++ b/core/src/main/kotlin/Model/ElementSignatureProvider.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dokka + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor + +interface ElementSignatureProvider { + fun signature(forDesc: DeclarationDescriptor): String + fun signature(forPsi: PsiElement): String +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Model/PackageDocs.kt b/core/src/main/kotlin/Model/PackageDocs.kt index 1f6bdcb9..9804f68b 100644 --- a/core/src/main/kotlin/Model/PackageDocs.kt +++ b/core/src/main/kotlin/Model/PackageDocs.kt @@ -2,16 +2,26 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.google.inject.Singleton +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.LocalTimeCounter +import com.intellij.openapi.util.text.StringUtil import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.parser.LinkMap +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import java.io.File @Singleton class PackageDocs @Inject constructor(val linkResolver: DeclarationLinkResolver?, - val logger: DokkaLogger) + val logger: DokkaLogger, + val environment: KotlinCoreEnvironment, + val refGraph: NodeReferenceGraph, + val elementSignatureProvider: ElementSignatureProvider) { val moduleContent: MutableContent = MutableContent() private val _packageContent: MutableMap<String, MutableContent> = hashMapOf() @@ -21,7 +31,7 @@ class PackageDocs fun parse(fileName: String, linkResolveContext: List<PackageFragmentDescriptor>) { val file = File(fileName) if (file.exists()) { - val text = file.readText() + val text = StringUtil.convertLineSeparators(file.readText()) val tree = parseMarkdown(text) val linkMap = LinkMap.buildLinkMap(tree.node, text) var targetContent: MutableContent = moduleContent @@ -40,6 +50,64 @@ class PackageDocs } } + private fun parseHtmlAsJavadoc(text: String, packageName: String, file: File) { + val javadocText = text + .replace("*/", "*/") + .removeSurrounding("<html>", "</html>", true).trim() + .removeSurrounding("<body>", "</body>", true) + .lineSequence() + .map { "* $it" } + .joinToString (separator = "\n", prefix = "/**\n", postfix = "\n*/") + parseJavadoc(javadocText, packageName, file) + } + + private fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence, ignoringCase: Boolean = false): CharSequence { + if ((length >= prefix.length + suffix.length) && startsWith(prefix, ignoringCase) && endsWith(suffix, ignoringCase)) { + return subSequence(prefix.length, length - suffix.length) + } + return subSequence(0, length) + } + + + private fun parseJavadoc(text: String, packageName: String, file: File) { + + val psiFileFactory = PsiFileFactory.getInstance(environment.project) + val psiFile = psiFileFactory.createFileFromText( + file.nameWithoutExtension + ".java", + JavaFileType.INSTANCE, + "package $packageName; $text\npublic class C {}", + LocalTimeCounter.currentTime(), + false, + true + ) + + val psiClass = PsiTreeUtil.getChildOfType(psiFile, PsiClass::class.java)!! + val parser = JavadocParser(refGraph, logger, elementSignatureProvider, linkResolver?.externalDocumentationLinkResolver!!) + findOrCreatePackageContent(packageName).apply { + val content = parser.parseDocumentation(psiClass).content + children.addAll(content.children) + content.sections.forEach { + addSection(it.tag, it.subjectName).children.addAll(it.children) + } + } + } + + + fun parseJava(fileName: String, packageName: String) { + val file = File(fileName) + if (file.exists()) { + val text = file.readText() + + val trimmedText = text.trim() + + if (trimmedText.startsWith("/**")) { + parseJavadoc(text, packageName, file) + } else if (trimmedText.toLowerCase().startsWith("<html>")) { + parseHtmlAsJavadoc(trimmedText, packageName, file) + } + } + } + private fun findTargetContent(heading: String): MutableContent { if (heading.startsWith("Module") || heading.startsWith("module")) { return moduleContent @@ -51,7 +119,7 @@ class PackageDocs } private fun findOrCreatePackageContent(packageName: String) = - _packageContent.getOrPut(packageName) { -> MutableContent() } + _packageContent.getOrPut(packageName) { MutableContent() } private fun resolveContentLink(fileName: String, href: String, linkResolveContext: List<PackageFragmentDescriptor>): ContentBlock { if (linkResolver != null) { diff --git a/core/src/main/kotlin/Model/SourceLinks.kt b/core/src/main/kotlin/Model/SourceLinks.kt index 2c75cfda..99ee362e 100644 --- a/core/src/main/kotlin/Model/SourceLinks.kt +++ b/core/src/main/kotlin/Model/SourceLinks.kt @@ -10,20 +10,20 @@ import java.io.File fun DocumentationNode.appendSourceLink(psi: PsiElement?, sourceLinks: List<SourceLinkDefinition>) { val path = psi?.containingFile?.virtualFile?.path ?: return + val canonicalPath = File(path).canonicalPath 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 pair = determineSourceLinkDefinition(canonicalPath, sourceLinks) + if (pair != null) { + val (sourceLinkDefinition, sourceLinkCanonicalPath) = pair + var url = determineUrl(canonicalPath, sourceLinkDefinition, sourceLinkCanonicalPath) + if (sourceLinkDefinition.lineSuffix != null) { val line = target?.lineNumber() if (line != null) { - url += linkDef.lineSuffix + line.toString() + url += sourceLinkDefinition.lineSuffix + line.toString() } } - append(DocumentationNode(url, Content.Empty, NodeKind.SourceUrl), - RefKind.Detail); + append(DocumentationNode(url, Content.Empty, NodeKind.SourceUrl), RefKind.Detail) } if (target != null) { @@ -31,6 +31,28 @@ fun DocumentationNode.appendSourceLink(psi: PsiElement?, sourceLinks: List<Sourc } } +private fun determineSourceLinkDefinition( + canonicalPath: String, + sourceLinks: List<SourceLinkDefinition> +): Pair<SourceLinkDefinition, String>? { + return sourceLinks + .asSequence() + .map { it to File(it.path).canonicalPath } + .firstOrNull { (_, sourceLinkCanonicalPath) -> + canonicalPath.startsWith(sourceLinkCanonicalPath) + } +} + +private fun determineUrl( + canonicalPath: String, + sourceLinkDefinition: SourceLinkDefinition, + sourceLinkCanonicalPath: String +): String { + val relativePath = canonicalPath.substring(sourceLinkCanonicalPath.length) + val relativeUrl = relativePath.replace('\\', '/').removePrefix("/") + return "${sourceLinkDefinition.url.removeSuffix("/")}/$relativeUrl" +} + private fun PsiElement.sourcePosition(): String { val path = containingFile.virtualFile.path val lineNumber = lineNumber() diff --git a/core/src/main/kotlin/Utilities/DokkaModules.kt b/core/src/main/kotlin/Utilities/DokkaModules.kt index 36704918..732cbc48 100644 --- a/core/src/main/kotlin/Utilities/DokkaModules.kt +++ b/core/src/main/kotlin/Utilities/DokkaModules.kt @@ -2,14 +2,11 @@ package org.jetbrains.dokka.Utilities import com.google.inject.Binder import com.google.inject.Module -import com.google.inject.Provider import com.google.inject.TypeLiteral import com.google.inject.binder.AnnotatedBindingBuilder import com.google.inject.name.Names import org.jetbrains.dokka.* import org.jetbrains.dokka.Formats.FormatDescriptor -import org.jetbrains.dokka.Model.DescriptorSignatureProvider -import org.jetbrains.dokka.Samples.SampleProcessingService import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import java.io.File import kotlin.reflect.KClass @@ -27,8 +24,9 @@ class DokkaAnalysisModule(val environment: AnalysisEnvironment, val coreEnvironment = environment.createCoreEnvironment() binder.bind<KotlinCoreEnvironment>().toInstance(coreEnvironment) - val dokkaResolutionFacade = environment.createResolutionFacade(coreEnvironment) + val (dokkaResolutionFacade, libraryResolutionFacade) = environment.createResolutionFacade(coreEnvironment) binder.bind<DokkaResolutionFacade>().toInstance(dokkaResolutionFacade) + binder.bind<DokkaResolutionFacade>().annotatedWith(Names.named("libraryResolutionFacade")).toInstance(libraryResolutionFacade) binder.bind<DocumentationOptions>().toInstance(options) diff --git a/core/src/main/kotlin/Utilities/Html.kt b/core/src/main/kotlin/Utilities/Html.kt index a5a93d9e..de1ce1a5 100644 --- a/core/src/main/kotlin/Utilities/Html.kt +++ b/core/src/main/kotlin/Utilities/Html.kt @@ -1,8 +1,12 @@ package org.jetbrains.dokka +import java.net.URLEncoder + /** * Replaces symbols reserved in HTML with their respective entities. * Replaces & with &, < with < and > with > */ fun String.htmlEscape(): String = replace("&", "&").replace("<", "<").replace(">", ">") + +fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8")
\ No newline at end of file diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt index 71bfd21b..83c4c65c 100644 --- a/core/src/main/kotlin/Utilities/ServiceLocator.kt +++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt @@ -14,12 +14,21 @@ class ServiceLookupException(message: String) : Exception(message) object ServiceLocator { fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T { val descriptor = lookupDescriptor(category, implementationName) + return lookup(clazz, descriptor) + } + + fun <T : Any> lookup( + clazz: Class<T>, + descriptor: ServiceDescriptor + ): T { val loadedClass = javaClass.classLoader.loadClass(descriptor.className) val constructor = loadedClass.constructors - .filter { it.parameterTypes.isEmpty() } - .firstOrNull() ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor") + .filter { it.parameterTypes.isEmpty() } + .firstOrNull() + ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor") - val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor) + val implementationRawType: Any = + if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor) if (!clazz.isInstance(implementationRawType)) { throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}") @@ -79,6 +88,7 @@ object ServiceLocator { } inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName) +inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc) private val ZipEntry.fileName: String get() = name.substringAfterLast("/", name) diff --git a/core/src/main/kotlin/Utilities/Uri.kt b/core/src/main/kotlin/Utilities/Uri.kt new file mode 100644 index 00000000..9827c624 --- /dev/null +++ b/core/src/main/kotlin/Utilities/Uri.kt @@ -0,0 +1,40 @@ +package org.jetbrains.dokka + +import java.net.URI + + +fun URI.relativeTo(uri: URI): URI { + // Normalize paths to remove . and .. segments + val base = uri.normalize() + val child = this.normalize() + + fun StringBuilder.appendRelativePath() { + // Split paths into segments + var bParts = base.path.split('/').dropLastWhile { it.isEmpty() } + val cParts = child.path.split('/').dropLastWhile { it.isEmpty() } + + // Discard trailing segment of base path + if (bParts.isNotEmpty() && !base.path.endsWith("/")) { + bParts = bParts.dropLast(1) + } + + // Compute common prefix + val commonPartsSize = bParts.zip(cParts).takeWhile { (basePart, childPart) -> basePart == childPart }.count() + bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" } + cParts.drop(commonPartsSize).joinTo(this, separator = "/") + } + + return URI.create(buildString { + if (base.path != child.path) { + appendRelativePath() + } + child.rawQuery?.let { + append("?") + append(it) + } + child.rawFragment?.let { + append("#") + append(it) + } + }) +}
\ No newline at end of file diff --git a/core/src/main/kotlin/javadoc/docbase.kt b/core/src/main/kotlin/javadoc/docbase.kt index 2a14c6ff..118b134a 100644 --- a/core/src/main/kotlin/javadoc/docbase.kt +++ b/core/src/main/kotlin/javadoc/docbase.kt @@ -2,7 +2,7 @@ package org.jetbrains.dokka.javadoc import com.sun.javadoc.* import org.jetbrains.dokka.* -import java.lang.reflect.Modifier +import java.lang.reflect.Modifier.* import java.util.* import kotlin.reflect.KClass @@ -75,9 +75,7 @@ open class DocumentationNodeAdapter(override val module: ModuleNodeAdapter, node node.deprecation?.let { val content = it.content.asText() - if (content != null) { - result.add(TagImpl(this, "deprecated", content)) - } + result.add(TagImpl(this, "deprecated", content ?: "")) } return result.toTypedArray() @@ -116,20 +114,27 @@ class AnnotationTypeDocAdapter(module: ModuleNodeAdapter, node: DocumentationNod } class AnnotationDescAdapter(val module: ModuleNodeAdapter, val node: DocumentationNode) : AnnotationDesc { - override fun annotationType(): AnnotationTypeDoc? = AnnotationTypeDocAdapter(module, node) // TODO ????? + override fun annotationType(): AnnotationTypeDoc? = AnnotationTypeDocAdapter(module, node.links.find { it.kind == NodeKind.AnnotationClass } ?: node) // TODO ????? override fun isSynthesized(): Boolean = false override fun elementValues(): Array<out AnnotationDesc.ElementValuePair>? = emptyArray() // TODO } open class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc { - override fun isPublic(): Boolean = true + override fun isPublic(): Boolean = node.hasModifier("public") || node.hasModifier("internal") 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 modifierSpecifier(): Int = visibilityModifier or (if (isStatic) STATIC else 0) + private val visibilityModifier + get() = when { + isPublic() -> PUBLIC + isPrivate() -> PRIVATE + isProtected() -> PROTECTED + else -> 0 + } override fun qualifiedName(): String? = 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 isProtected(): Boolean = node.hasModifier("protected") override fun isFinal(): Boolean = node.hasModifier("final") @@ -165,7 +170,7 @@ open class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationN return null } - override fun isPrivate(): Boolean = false + override fun isPrivate(): Boolean = node.hasModifier("private") override fun isIncluded(): Boolean = containingPackage()?.isIncluded ?: false && containingClass()?.let { it.isIncluded } ?: true } @@ -173,7 +178,7 @@ open class TypeAdapter(override val module: ModuleNodeAdapter, override val node private val javaLanguageService = JavaLanguageService() override fun qualifiedTypeName(): String = javaLanguageService.getArrayElementType(node)?.qualifiedNameFromType() ?: node.qualifiedNameFromType() - override fun typeName(): String = javaLanguageService.getArrayElementType(node)?.simpleName() ?: node.simpleName() + override fun typeName(): String = (javaLanguageService.getArrayElementType(node)?.simpleName() ?: node.simpleName()) + dimension() override fun simpleTypeName(): String = typeName() // TODO difference typeName() vs simpleTypeName() override fun dimension(): String = Collections.nCopies(javaLanguageService.getArrayDimension(node), "[]").joinToString("") @@ -275,7 +280,7 @@ class ParameterizedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNod } class ParameterAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), Parameter { - override fun typeName(): String? = JavaLanguageService().renderType(node.detail(NodeKind.Type)) + override fun typeName(): String? = type()?.typeName() override fun type(): Type? = TypeAdapter(module, node.detail(NodeKind.Type)) override fun annotations(): Array<out AnnotationDesc> = nodeAnnotations(this).toTypedArray() } @@ -317,7 +322,7 @@ open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: Documentatio .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it.subjectName!!, NodeKind.Exception)), it.children) } .toTypedArray() - override fun isVarArgs(): Boolean = node.details(NodeKind.Parameter).any { false } // TODO + override fun isVarArgs(): Boolean = node.details(NodeKind.Parameter).last().hasModifier("vararg") override fun isSynchronized(): Boolean = node.annotations.any { it.name == "synchronized" } @@ -406,6 +411,9 @@ open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNod return classNode.simpleName() } + override fun qualifiedName(): String? { + return super.qualifiedName() + } override fun constructors(filter: Boolean): Array<out ConstructorDoc> = classNode.members(NodeKind.Constructor).map { ConstructorAdapter(module, it) }.toTypedArray() override fun constructors(): Array<out ConstructorDoc> = constructors(true) override fun importedPackages(): Array<out PackageDoc> = emptyArray() @@ -514,12 +522,13 @@ class ModuleNodeAdapter(val module: DocumentationModule, val reporter: DocErrorR } private fun DocumentationNodeAdapter.collectParamTags(kind: NodeKind, sectionFilter: (ContentSection) -> Boolean) = - (node.details(kind) - .filter(DocumentationNode::hasNonEmptyContent) - .map { ParamTagAdapter(module, this, it.name, true, it.content.children) } + (node.details(kind) + .filter(DocumentationNode::hasNonEmptyContent) + .map { ParamTagAdapter(module, this, it.name, true, it.content.children) } - + node.content.sections - .filter(sectionFilter) - .map { ParamTagAdapter(module, this, it.subjectName ?: "?", true, it.children) }) - - .toTypedArray()
\ No newline at end of file + + node.content.sections + .filter(sectionFilter) + .map { ParamTagAdapter(module, this, it.subjectName ?: "?", true, it.children) } + ) + .distinctBy { it.parameterName } + .toTypedArray()
\ No newline at end of file diff --git a/core/src/main/kotlin/javadoc/tags.kt b/core/src/main/kotlin/javadoc/tags.kt index 05d98b71..99c9bfff 100644 --- a/core/src/main/kotlin/javadoc/tags.kt +++ b/core/src/main/kotlin/javadoc/tags.kt @@ -71,7 +71,7 @@ class SeeMethodTagAdapter(holder: Doc, val method: MethodAdapter, content: Conte 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 label(): String = content.text ?: "${method.containingClass()?.name()}.${method.name()}" override fun inlineTags(): Array<out Tag> = emptyArray() // TODO override fun firstSentenceTags(): Array<out Tag> = inlineTags() // TODO diff --git a/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties new file mode 100644 index 00000000..c484a920 --- /dev/null +++ b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Dokka +description=Uses Dokka Default resolver
\ No newline at end of file diff --git a/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties new file mode 100644 index 00000000..3b61eabe --- /dev/null +++ b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.Formats.JavaLayoutHtmlInboundLinkResolutionService +description=Resolver for JavaLayoutHtml
\ No newline at end of file diff --git a/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties new file mode 100644 index 00000000..0d5d7d17 --- /dev/null +++ b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties @@ -0,0 +1,2 @@ +class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Javadoc +description=Uses Javadoc Default resolver
\ No newline at end of file diff --git a/core/src/test/kotlin/NodeSelect.kt b/core/src/test/kotlin/NodeSelect.kt new file mode 100644 index 00000000..fe0394f9 --- /dev/null +++ b/core/src/test/kotlin/NodeSelect.kt @@ -0,0 +1,90 @@ +package org.jetbrains.dokka.tests + +import org.jetbrains.dokka.DocumentationNode +import org.jetbrains.dokka.NodeKind +import org.jetbrains.dokka.RefKind + +class SelectBuilder { + private val root = ChainFilterNode(SubgraphTraverseFilter(), null) + private var activeNode = root + private val chainEnds = mutableListOf<SelectFilter>() + + fun withName(name: String) = matching { it.name == name } + + fun withKind(kind: NodeKind) = matching{ it.kind == kind } + + fun matching(block: (DocumentationNode) -> Boolean) { + attachFilterAndMakeActive(PredicateFilter(block)) + } + + fun subgraph() { + attachFilterAndMakeActive(SubgraphTraverseFilter()) + } + + fun subgraphOf(kind: RefKind) { + attachFilterAndMakeActive(DirectEdgeFilter(kind)) + } + + private fun attachFilterAndMakeActive(next: SelectFilter) { + activeNode = ChainFilterNode(next, activeNode) + } + + private fun endChain() { + chainEnds += activeNode + } + + fun build(): SelectFilter { + endChain() + return CombineFilterNode(chainEnds) + } +} + +private class ChainFilterNode(val filter: SelectFilter, val previous: SelectFilter?): SelectFilter() { + override fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> { + return filter.select(previous?.select(roots) ?: roots) + } +} + +private class CombineFilterNode(val previous: List<SelectFilter>): SelectFilter() { + override fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> { + return previous.asSequence().flatMap { it.select(roots) } + } +} + +abstract class SelectFilter { + abstract fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> +} + +private class SubgraphTraverseFilter: SelectFilter() { + override fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> { + val visited = mutableSetOf<DocumentationNode>() + return roots.flatMap { + generateSequence(listOf(it)) { nodes -> + nodes.flatMap { it.allReferences() } + .map { it.to } + .filter { visited.add(it) } + .takeUnless { it.isEmpty() } + } + }.flatten() + } + +} + +private class PredicateFilter(val condition: (DocumentationNode) -> Boolean): SelectFilter() { + override fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> { + return roots.filter(condition) + } +} + +private class DirectEdgeFilter(val kind: RefKind): SelectFilter() { + override fun select(roots: Sequence<DocumentationNode>): Sequence<DocumentationNode> { + return roots.flatMap { it.references(kind).asSequence() }.map { it.to } + } +} + + +fun selectNodes(root: DocumentationNode, block: SelectBuilder.() -> Unit): List<DocumentationNode> { + val builder = SelectBuilder() + builder.apply(block) + return builder.build().select(sequenceOf(root)).toMutableSet().toList() +}
\ No newline at end of file diff --git a/core/src/test/kotlin/TestAPI.kt b/core/src/test/kotlin/TestAPI.kt index aa3eff48..4f77a2f6 100644 --- a/core/src/test/kotlin/TestAPI.kt +++ b/core/src/test/kotlin/TestAPI.kt @@ -8,12 +8,12 @@ import com.intellij.rt.execution.junit.FileComparisonFailure import org.jetbrains.dokka.* import org.jetbrains.dokka.DokkaConfiguration.SourceLinkDefinition import org.jetbrains.dokka.Utilities.DokkaAnalysisModule +import org.jetbrains.kotlin.cli.common.config.ContentRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot 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.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.junit.Assert import org.junit.Assert.fail @@ -25,22 +25,27 @@ fun verifyModel(vararg roots: ContentRoot, format: String = "html", includeNonPublic: Boolean = true, perPackageOptions: List<DokkaConfiguration.PackageOptions> = emptyList(), + noStdlibLink: Boolean = true, + collectInheritedExtensionsFromLibraries: Boolean = false, + sourceLinks: List<SourceLinkDefinition> = emptyList(), verifier: (DocumentationModule) -> Unit) { val documentation = DocumentationModule("test") val options = DocumentationOptions( - "", - format, - includeNonPublic = includeNonPublic, - skipEmptyPackages = false, - includeRootPackage = true, - sourceLinks = listOf(), - perPackageOptions = perPackageOptions, - generateIndexPages = false, - noStdlibLink = true, - cacheRoot = "default", - languageVersion = null, - apiVersion = null + "", + format, + includeNonPublic = includeNonPublic, + skipEmptyPackages = false, + includeRootPackage = true, + sourceLinks = sourceLinks, + perPackageOptions = perPackageOptions, + generateIndexPages = false, + noStdlibLink = noStdlibLink, + noJdkLink = false, + cacheRoot = "default", + languageVersion = null, + apiVersion = null, + collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries ) appendDocumentation(documentation, *roots, @@ -110,15 +115,17 @@ fun verifyModel(source: String, withKotlinRuntime: Boolean = false, format: String = "html", includeNonPublic: Boolean = true, + sourceLinks: List<SourceLinkDefinition> = emptyList(), verifier: (DocumentationModule) -> Unit) { - if (!File(source).exists()) { - throw IllegalArgumentException("Can't find test data file $source") + require (File(source).exists()) { + "Cannot find test data file $source" } verifyModel(contentRootFromPath(source), withJdk = withJdk, withKotlinRuntime = withKotlinRuntime, format = format, includeNonPublic = includeNonPublic, + sourceLinks = sourceLinks, verifier = verifier) } @@ -161,13 +168,17 @@ fun verifyOutput(roots: Array<ContentRoot>, withKotlinRuntime: Boolean = false, format: String = "html", includeNonPublic: Boolean = true, + noStdlibLink: Boolean = true, + collectInheritedExtensionsFromLibraries: Boolean = false, outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { verifyModel( - *roots, - withJdk = withJdk, - withKotlinRuntime = withKotlinRuntime, - format = format, - includeNonPublic = includeNonPublic + *roots, + withJdk = withJdk, + withKotlinRuntime = withKotlinRuntime, + format = format, + includeNonPublic = includeNonPublic, + noStdlibLink = noStdlibLink, + collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries ) { verifyModelOutput(it, outputExtension, roots.first().path, outputGenerator) } @@ -184,21 +195,27 @@ fun verifyModelOutput(it: DocumentationModule, assertEqualsIgnoringSeparators(expectedFile, output.toString()) } -fun verifyOutput(path: String, - outputExtension: String, - withJdk: Boolean = false, - withKotlinRuntime: Boolean = false, - format: String = "html", - includeNonPublic: Boolean = true, - outputGenerator: (DocumentationModule, StringBuilder) -> Unit) { +fun verifyOutput( + path: String, + outputExtension: String, + withJdk: Boolean = false, + withKotlinRuntime: Boolean = false, + format: String = "html", + includeNonPublic: Boolean = true, + noStdlibLink: Boolean = true, + collectInheritedExtensionsFromLibraries: Boolean = false, + outputGenerator: (DocumentationModule, StringBuilder) -> Unit +) { verifyOutput( - arrayOf(contentRootFromPath(path)), - outputExtension, - withJdk, - withKotlinRuntime, - format, - includeNonPublic, - outputGenerator + arrayOf(contentRootFromPath(path)), + outputExtension, + withJdk, + withKotlinRuntime, + format, + includeNonPublic, + noStdlibLink, + collectInheritedExtensionsFromLibraries, + outputGenerator ) } @@ -257,6 +274,14 @@ fun StringBuilder.appendNode(node: ContentNode): StringBuilder { is ContentBlock -> { appendChildren(node) } + is NodeRenderContent -> { + append("render(") + append(node.node) + append(",") + append(node.mode) + append(")") + } + is ContentSymbol -> { append(node.text) } is ContentEmpty -> { /* nothing */ } else -> throw IllegalStateException("Don't know how to format node $node") } diff --git a/core/src/test/kotlin/format/HtmlFormatTest.kt b/core/src/test/kotlin/format/HtmlFormatTest.kt index 54c367fd..807118c5 100644 --- a/core/src/test/kotlin/format/HtmlFormatTest.kt +++ b/core/src/test/kotlin/format/HtmlFormatTest.kt @@ -1,9 +1,8 @@ package org.jetbrains.dokka.tests import org.jetbrains.dokka.* +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot -import org.jetbrains.kotlin.config.KotlinSourceRoot -import org.junit.Before import org.junit.Test import java.io.File @@ -100,7 +99,8 @@ class HtmlFormatTest: FileGeneratorTestCase() { } @Test fun crossLanguageKotlinExtendsJava() { - verifyOutput(arrayOf(KotlinSourceRoot("testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt"), + verifyOutput(arrayOf( + KotlinSourceRoot("testdata/format/crossLanguage/kotlinExtendsJava/Bar.kt", false), JavaSourceRoot(File("testdata/format/crossLanguage/kotlinExtendsJava"), null)), ".html") { model, output -> buildPagesAndReadInto( diff --git a/core/src/test/kotlin/format/KotlinWebSiteFormatTest.kt b/core/src/test/kotlin/format/KotlinWebSiteFormatTest.kt index b971b54d..062cf0b5 100644 --- a/core/src/test/kotlin/format/KotlinWebSiteFormatTest.kt +++ b/core/src/test/kotlin/format/KotlinWebSiteFormatTest.kt @@ -54,6 +54,7 @@ class KotlinWebSiteFormatTest: FileGeneratorTestCase() { outputFormat = "html", generateIndexPages = false, noStdlibLink = true, + noJdkLink = true, languageVersion = null, apiVersion = null ) diff --git a/core/src/test/kotlin/format/KotlinWebSiteHtmlFormatTest.kt b/core/src/test/kotlin/format/KotlinWebSiteHtmlFormatTest.kt index 49fa6d2f..8076fb92 100644 --- a/core/src/test/kotlin/format/KotlinWebSiteHtmlFormatTest.kt +++ b/core/src/test/kotlin/format/KotlinWebSiteHtmlFormatTest.kt @@ -65,6 +65,7 @@ class KotlinWebSiteHtmlFormatTest: FileGeneratorTestCase() { outputFormat = "kotlin-website-html", generateIndexPages = false, noStdlibLink = true, + noJdkLink = true, languageVersion = null, apiVersion = null ) diff --git a/core/src/test/kotlin/format/MarkdownFormatTest.kt b/core/src/test/kotlin/format/MarkdownFormatTest.kt index 9e4c831d..b078292b 100644 --- a/core/src/test/kotlin/format/MarkdownFormatTest.kt +++ b/core/src/test/kotlin/format/MarkdownFormatTest.kt @@ -109,7 +109,12 @@ class MarkdownFormatTest: FileGeneratorTestCase() { } @Test fun javaCodeInParam() { - verifyJavaMarkdownNode("javaCodeInParam") + verifyJavaMarkdownNodes("javaCodeInParam") { + selectNodes(it) { + subgraphOf(RefKind.Member) + withKind(NodeKind.Function) + } + } } @Test fun javaSpaceInAuthor() { @@ -160,6 +165,14 @@ class MarkdownFormatTest: FileGeneratorTestCase() { verifyMarkdownNode("reifiedTypeParameter", withKotlinRuntime = true) } + @Test fun suspendInlineFunctionOrder() { + verifyMarkdownNode("suspendInlineFunction", withKotlinRuntime = true) + } + + @Test fun inlineSuspendFunctionOrderChanged() { + verifyMarkdownNode("inlineSuspendFunction", withKotlinRuntime = true) + } + @Test fun annotatedTypeParameter() { verifyMarkdownNode("annotatedTypeParameter", withKotlinRuntime = true) } @@ -313,6 +326,7 @@ class MarkdownFormatTest: FileGeneratorTestCase() { outputFormat = "html", generateIndexPages = false, noStdlibLink = true, + noJdkLink = true, languageVersion = null, apiVersion = null ) @@ -447,6 +461,7 @@ class MarkdownFormatTest: FileGeneratorTestCase() { outputFormat = "html", generateIndexPages = false, noStdlibLink = true, + noJdkLink = true, languageVersion = null, apiVersion = null ) @@ -524,4 +539,8 @@ class MarkdownFormatTest: FileGeneratorTestCase() { nodesWithName } } + + @Test fun nullableTypeParameterFunction() { + verifyMarkdownNode("nullableTypeParameterFunction", withKotlinRuntime = true) + } } diff --git a/core/src/test/kotlin/format/PackageDocsTest.kt b/core/src/test/kotlin/format/PackageDocsTest.kt index 704f7b99..3ff5f123 100644 --- a/core/src/test/kotlin/format/PackageDocsTest.kt +++ b/core/src/test/kotlin/format/PackageDocsTest.kt @@ -1,19 +1,46 @@ package org.jetbrains.dokka.tests.format +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doAnswer import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.mock import org.jetbrains.dokka.* import org.jetbrains.dokka.tests.assertEqualsIgnoringSeparators +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Test import java.io.File class PackageDocsTest { + + private lateinit var testDisposable: Disposable + + @Before + fun setup() { + testDisposable = Disposer.newDisposable() + } + + @After + fun cleanup() { + Disposer.dispose(testDisposable) + } + + fun createPackageDocs(linkResolver: DeclarationLinkResolver?): PackageDocs { + val environment = KotlinCoreEnvironment.createForTests(testDisposable, CompilerConfiguration.EMPTY, EnvironmentConfigFiles.JVM_CONFIG_FILES) + return PackageDocs(linkResolver, DokkaConsoleLogger, environment, mock(), mock()) + } + @Test fun verifyParse() { - val docs = PackageDocs(null, DokkaConsoleLogger) + + val docs = createPackageDocs(null) docs.parse("testdata/packagedocs/stdlib.md", emptyList()) val packageContent = docs.packageContent["kotlin"]!! val block = (packageContent.children.single() as ContentBlock).children.first() as ContentText @@ -22,13 +49,13 @@ class PackageDocsTest { @Test fun testReferenceLinksInPackageDocs() { val mockLinkResolver = mock<DeclarationLinkResolver> { - val exampleCom = "http://example.com" + val exampleCom = "https://example.com" on { tryResolveContentLink(any(), eq(exampleCom)) } doAnswer { ContentExternalLink(exampleCom) } } val mockPackageDescriptor = mock<PackageFragmentDescriptor> {} - val docs = PackageDocs(mockLinkResolver, DokkaConsoleLogger) + val docs = createPackageDocs(mockLinkResolver) docs.parse("testdata/packagedocs/referenceLinks.md", listOf(mockPackageDescriptor)) checkMarkdownOutput(docs, "testdata/packagedocs/referenceLinks") diff --git a/core/src/test/kotlin/javadoc/JavadocTest.kt b/core/src/test/kotlin/javadoc/JavadocTest.kt index 45c45aa4..6adbe7a0 100644 --- a/core/src/test/kotlin/javadoc/JavadocTest.kt +++ b/core/src/test/kotlin/javadoc/JavadocTest.kt @@ -7,6 +7,7 @@ import org.jetbrains.dokka.tests.assertEqualsIgnoringSeparators import org.jetbrains.dokka.tests.verifyModel import org.junit.Assert.* import org.junit.Test +import java.lang.reflect.Modifier.* class JavadocTest { @Test fun testTypes() { @@ -64,7 +65,7 @@ class JavadocTest { val member = classDoc.methods().find { it.name() == "main" }!! val paramType = member.parameters()[0].type() assertNull(paramType.asParameterizedType()) - assertEquals("String", paramType.typeName()) + assertEquals("String[]", paramType.typeName()) assertEquals("String", paramType.asClassDoc().name()) } } @@ -161,6 +162,115 @@ class JavadocTest { } } + @Test + fun testVararg() { + verifyJavadoc("testdata/javadoc/vararg.kt") { doc -> + val classDoc = doc.classNamed("VarargKt")!! + val methods = classDoc.methods() + methods.single { it.name() == "vararg" }.let { method -> + assertTrue(method.isVarArgs) + assertEquals("int", method.parameters().last().typeName()) + } + methods.single { it.name() == "varargInMiddle" }.let { method -> + assertFalse(method.isVarArgs) + assertEquals("int[]", method.parameters()[1].typeName()) + } + } + } + + @Test + fun shouldHaveValidVisibilityModifiers() { + verifyJavadoc("testdata/javadoc/visibilityModifiers.kt", withKotlinRuntime = true) { doc -> + val classDoc = doc.classNamed("foo.Apple")!! + val methods = classDoc.methods() + + val getName = methods[0] + val setName = methods[1] + val getWeight = methods[2] + val setWeight = methods[3] + val getRating = methods[4] + val setRating = methods[5] + val getCode = methods[6] + val color = classDoc.fields()[3] + val code = classDoc.fields()[4] + + assertTrue(getName.isProtected) + assertEquals(PROTECTED, getName.modifierSpecifier()) + assertTrue(setName.isProtected) + assertEquals(PROTECTED, setName.modifierSpecifier()) + + assertTrue(getWeight.isPublic) + assertEquals(PUBLIC, getWeight.modifierSpecifier()) + assertTrue(setWeight.isPublic) + assertEquals(PUBLIC, setWeight.modifierSpecifier()) + + assertTrue(getRating.isPublic) + assertEquals(PUBLIC, getRating.modifierSpecifier()) + assertTrue(setRating.isPublic) + assertEquals(PUBLIC, setRating.modifierSpecifier()) + + assertTrue(getCode.isPublic) + assertEquals(PUBLIC or STATIC, getCode.modifierSpecifier()) + + assertEquals(methods.size, 7) + + assertTrue(color.isPrivate) + assertEquals(PRIVATE, color.modifierSpecifier()) + + assertTrue(code.isPrivate) + assertTrue(code.isStatic) + assertEquals(PRIVATE or STATIC, code.modifierSpecifier()) + } + } + + @Test + fun shouldNotHaveDuplicatedConstructorParameters() { + verifyJavadoc("testdata/javadoc/constructorParameters.kt") { doc -> + val classDoc = doc.classNamed("bar.Banana")!! + val paramTags = classDoc.constructors()[0].paramTags() + + assertEquals(3, paramTags.size) + } + } + + @Test fun shouldHaveAllFunctionMarkedAsDeprecated() { + verifyJavadoc("testdata/javadoc/deprecated.java") { doc -> + val classDoc = doc.classNamed("bar.Banana")!! + + classDoc.methods().forEach { method -> + assertTrue(method.tags().any { it.kind() == "deprecated" }) + } + } + } + + @Test + fun testDefaultNoArgConstructor() { + verifyJavadoc("testdata/javadoc/defaultNoArgConstructor.kt") { doc -> + val classDoc = doc.classNamed("foo.Peach")!! + assertTrue(classDoc.constructors()[0].tags()[2].text() == "print peach") + } + } + + @Test + fun testNoArgConstructor() { + verifyJavadoc("testdata/javadoc/noArgConstructor.kt") { doc -> + val classDoc = doc.classNamed("foo.Plum")!! + assertTrue(classDoc.constructors()[0].tags()[2].text() == "print plum") + } + } + + @Test + fun testArgumentReference() { + verifyJavadoc("testdata/javadoc/argumentReference.kt") { doc -> + val classDoc = doc.classNamed("ArgumentReferenceKt")!! + val method = classDoc.methods().first() + val tag = method.seeTags().first() + assertEquals("argNamedError", tag.referencedMemberName()) + assertEquals("error", tag.label()) + } + } + + private fun verifyJavadoc(name: String, withJdk: Boolean = false, withKotlinRuntime: Boolean = false, diff --git a/core/src/test/kotlin/model/FunctionTest.kt b/core/src/test/kotlin/model/FunctionTest.kt index 32910682..fd7a16a4 100644 --- a/core/src/test/kotlin/model/FunctionTest.kt +++ b/core/src/test/kotlin/model/FunctionTest.kt @@ -167,6 +167,33 @@ Documentation""", content.description.toTestString()) } } + @Test fun suspendFunction() { + verifyPackageMember("testdata/functions/suspendFunction.kt") { func -> + val modifiers = func.details(NodeKind.Modifier).map { it.name } + assertTrue("suspend" in modifiers) + } + } + + @Test fun suspendInlineFunctionOrder() { + verifyPackageMember("testdata/functions/suspendInlineFunction.kt") { func -> + val modifiers = func.details(NodeKind.Modifier).map { it.name }.filter { + it == "suspend" || it == "inline" + } + + assertEquals(listOf("suspend", "inline"), modifiers) + } + } + + @Test fun inlineSuspendFunctionOrderChanged() { + verifyPackageMember("testdata/functions/inlineSuspendFunction.kt") { func -> + val modifiers = func.details(NodeKind.Modifier).map { it.name }.filter { + it == "suspend" || it == "inline" + } + + assertEquals(listOf("suspend", "inline"), modifiers) + } + } + @Test fun functionWithAnnotatedParam() { verifyModel("testdata/functions/functionWithAnnotatedParam.kt") { model -> with(model.members.single().members.single { it.name == "function" }) { diff --git a/core/src/test/kotlin/model/JavaTest.kt b/core/src/test/kotlin/model/JavaTest.kt index e6c22ee4..0bec6d01 100644 --- a/core/src/test/kotlin/model/JavaTest.kt +++ b/core/src/test/kotlin/model/JavaTest.kt @@ -18,12 +18,12 @@ public class JavaTest { with(content.sections[0]) { assertEquals("Parameters", tag) assertEquals("name", subjectName) - assertEquals("is String parameter", toTestString()) + assertEquals("render(Type:String,SUMMARY): is String parameter", toTestString()) } with(content.sections[1]) { assertEquals("Parameters", tag) assertEquals("value", subjectName) - assertEquals("is int parameter", toTestString()) + assertEquals("render(Type:Int,SUMMARY): is int parameter", toTestString()) } with(content.sections[2]) { assertEquals("Author", tag) @@ -150,7 +150,7 @@ public class JavaTest { /** * `@suppress` not supported in Java! * - * [Proposed tags](http://www.oracle.com/technetwork/java/javase/documentation/proposed-tags-142378.html) + * [Proposed tags](https://www.oracle.com/technetwork/java/javase/documentation/proposed-tags-142378.html) * Proposed tag `@exclude` for it, but not supported yet */ @Ignore("@suppress not supported in Java!") @Test fun suppressTag() { @@ -193,7 +193,7 @@ public class JavaTest { @Test fun enumValues() { verifyJavaPackageMember("testdata/java/enumValues.java") { cls -> val superTypes = cls.details(NodeKind.Supertype) - assertEquals(0, superTypes.size) + assertEquals(1, superTypes.size) assertEquals(1, cls.members(NodeKind.EnumItem).size) } } diff --git a/core/src/test/kotlin/model/KotlinAsJavaTest.kt b/core/src/test/kotlin/model/KotlinAsJavaTest.kt index d24d8bdd..22818038 100644 --- a/core/src/test/kotlin/model/KotlinAsJavaTest.kt +++ b/core/src/test/kotlin/model/KotlinAsJavaTest.kt @@ -2,6 +2,8 @@ package org.jetbrains.dokka.tests import org.jetbrains.dokka.DocumentationModule import org.jetbrains.dokka.NodeKind +import org.jetbrains.dokka.RefKind +import org.junit.Assert import org.junit.Test import org.junit.Assert.assertEquals @@ -27,6 +29,24 @@ class KotlinAsJavaTest { assertEquals("doc", getter.content.summary.toTestString()) } } + + + @Test fun constants() { + verifyModelAsJava("testdata/java/constants.java") { cls -> + selectNodes(cls) { + subgraphOf(RefKind.Member) + matching { it.name == "constStr" || it.name == "refConst" } + }.forEach { + assertEquals("In $it", "\"some value\"", it.detailOrNull(NodeKind.Value)?.name) + } + val nullConstNode = selectNodes(cls) { + subgraphOf(RefKind.Member) + withName("nullConst") + }.single() + + Assert.assertNull(nullConstNode.detailOrNull(NodeKind.Value)) + } + } } fun verifyModelAsJava(source: String, diff --git a/core/src/test/kotlin/model/PackageTest.kt b/core/src/test/kotlin/model/PackageTest.kt index 052f0d28..7a8f0d06 100644 --- a/core/src/test/kotlin/model/PackageTest.kt +++ b/core/src/test/kotlin/model/PackageTest.kt @@ -3,7 +3,7 @@ package org.jetbrains.dokka.tests import org.jetbrains.dokka.Content import org.jetbrains.dokka.NodeKind import org.jetbrains.dokka.PackageOptionsImpl -import org.jetbrains.kotlin.config.KotlinSourceRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot import org.junit.Assert.* import org.junit.Test @@ -48,8 +48,8 @@ public class PackageTest { } @Test fun multipleFiles() { - verifyModel(KotlinSourceRoot("testdata/packages/dottedNamePackage.kt"), - KotlinSourceRoot("testdata/packages/simpleNamePackage.kt")) { model -> + verifyModel(KotlinSourceRoot("testdata/packages/dottedNamePackage.kt", false), + KotlinSourceRoot("testdata/packages/simpleNamePackage.kt", false)) { model -> assertEquals(2, model.members.count()) with(model.members.single { it.name == "simple" }) { assertEquals(NodeKind.Package, kind) @@ -70,8 +70,8 @@ public class PackageTest { } @Test fun multipleFilesSamePackage() { - verifyModel(KotlinSourceRoot("testdata/packages/simpleNamePackage.kt"), - KotlinSourceRoot("testdata/packages/simpleNamePackage2.kt")) { model -> + verifyModel(KotlinSourceRoot("testdata/packages/simpleNamePackage.kt", false), + KotlinSourceRoot("testdata/packages/simpleNamePackage2.kt", false)) { model -> assertEquals(1, model.members.count()) with(model.members.elementAt(0)) { assertEquals(NodeKind.Package, kind) @@ -85,7 +85,7 @@ public class PackageTest { } @Test fun classAtPackageLevel() { - verifyModel(KotlinSourceRoot("testdata/packages/classInPackage.kt")) { model -> + verifyModel(KotlinSourceRoot("testdata/packages/classInPackage.kt", false)) { model -> assertEquals(1, model.members.count()) with(model.members.elementAt(0)) { assertEquals(NodeKind.Package, kind) @@ -99,7 +99,7 @@ public class PackageTest { } @Test fun suppressAtPackageLevel() { - verifyModel(KotlinSourceRoot("testdata/packages/classInPackage.kt"), + verifyModel(KotlinSourceRoot("testdata/packages/classInPackage.kt", false), perPackageOptions = listOf(PackageOptionsImpl(prefix = "simple.name", suppress = true))) { model -> assertEquals(1, model.members.count()) with(model.members.elementAt(0)) { diff --git a/core/src/test/kotlin/model/PropertyTest.kt b/core/src/test/kotlin/model/PropertyTest.kt index 0ee0e0f5..296eaa4c 100644 --- a/core/src/test/kotlin/model/PropertyTest.kt +++ b/core/src/test/kotlin/model/PropertyTest.kt @@ -69,7 +69,7 @@ class PropertyTest { with(model.members.single().members.single()) { assertEquals(1, annotations.count()) with(annotations[0]) { - assertEquals("Volatile", name) + assertEquals("Strictfp", name) assertEquals(Content.Empty, content) assertEquals(NodeKind.Annotation, kind) } diff --git a/core/src/test/kotlin/model/SourceLinksErrorTest.kt b/core/src/test/kotlin/model/SourceLinksErrorTest.kt new file mode 100644 index 00000000..a72bd62a --- /dev/null +++ b/core/src/test/kotlin/model/SourceLinksErrorTest.kt @@ -0,0 +1,34 @@ +package org.jetbrains.dokka.tests.model + +import org.jetbrains.dokka.NodeKind +import org.jetbrains.dokka.SourceLinkDefinitionImpl +import org.jetbrains.dokka.tests.verifyModel +import org.junit.Assert +import org.junit.Test +import java.io.File + +class SourceLinksErrorTest { + + @Test + fun absolutePath_notMatching() { + val sourceLink = SourceLinkDefinitionImpl(File("testdata/nonExisting").absolutePath, "http://...", null) + verifyNoSourceUrl(sourceLink) + } + + @Test + fun relativePath_notMatching() { + val sourceLink = SourceLinkDefinitionImpl("testdata/nonExisting", "http://...", null) + verifyNoSourceUrl(sourceLink) + } + + private fun verifyNoSourceUrl(sourceLink: SourceLinkDefinitionImpl) { + verifyModel("testdata/sourceLinks/dummy.kt", sourceLinks = listOf(sourceLink)) { model -> + with(model.members.single().members.single()) { + Assert.assertEquals("foo", name) + Assert.assertEquals(NodeKind.Function, kind) + Assert.assertTrue("should not have source urls", details(NodeKind.SourceUrl).isEmpty()) + } + } + } +} + diff --git a/core/src/test/kotlin/model/SourceLinksTest.kt b/core/src/test/kotlin/model/SourceLinksTest.kt new file mode 100644 index 00000000..0e9b666c --- /dev/null +++ b/core/src/test/kotlin/model/SourceLinksTest.kt @@ -0,0 +1,74 @@ +package org.jetbrains.dokka.tests.model + +import org.jetbrains.dokka.NodeKind +import org.jetbrains.dokka.SourceLinkDefinitionImpl +import org.jetbrains.dokka.tests.verifyModel +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.File + +@RunWith(Parameterized::class) +class SourceLinksTest( + private val srcLink: String, + private val url: String, + private val lineSuffix: String?, + private val expectedUrl: String +) { + + @Test + fun test() { + val link = if(srcLink.contains(sourceLinks)){ + srcLink.substringBeforeLast(sourceLinks) + sourceLinks + } else { + srcLink.substringBeforeLast(testdata) + testdata + } + val sourceLink = SourceLinkDefinitionImpl(link, url, lineSuffix) + + verifyModel(filePath, sourceLinks = listOf(sourceLink)) { model -> + with(model.members.single().members.single()) { + Assert.assertEquals("foo", name) + Assert.assertEquals(NodeKind.Function, kind) + Assert.assertEquals(expectedUrl, details(NodeKind.SourceUrl).single().name) + } + } + } + + companion object { + private const val testdata = "testdata" + private const val sourceLinks = "sourceLinks" + private const val dummy = "dummy.kt" + private const val pathSuffix = "$sourceLinks/$dummy" + private const val filePath = "$testdata/$pathSuffix" + private const val url = "https://example.com" + + @Parameterized.Parameters(name = "{index}: {0}, {1}, {2} = {3}") + @JvmStatic + fun data(): Collection<Array<String?>> { + val longestPath = File(testdata).absolutePath.removeSuffix("/") + "/../$testdata/" + val maxLength = longestPath.length + val list = listOf( + arrayOf(File(testdata).absolutePath.removeSuffix("/"), "$url/$pathSuffix"), + arrayOf(File("$testdata/$sourceLinks").absolutePath.removeSuffix("/") + "/", "$url/$dummy"), + arrayOf(longestPath, "$url/$pathSuffix"), + + arrayOf(testdata, "$url/$pathSuffix"), + arrayOf("./$testdata", "$url/$pathSuffix"), + arrayOf("../core/$testdata", "$url/$pathSuffix"), + arrayOf("$testdata/$sourceLinks", "$url/$dummy"), + arrayOf("./$testdata/../$testdata/$sourceLinks", "$url/$dummy") + ) + + return list.map { arrayOf(it[0].padEnd(maxLength, '_'), url, null, it[1]) } + + listOf( + // check that it also works if url ends with / + arrayOf((File(testdata).absolutePath.removeSuffix("/") + "/").padEnd(maxLength, '_'), "$url/", null, "$url/$pathSuffix"), + // check if line suffix work + arrayOf<String?>("../core/../core/./$testdata/$sourceLinks/".padEnd(maxLength, '_'), "$url/", "#L", "$url/$dummy#L4") + ) + } + } + +} + diff --git a/core/testdata/format/JavaSupertype.html b/core/testdata/format/JavaSupertype.html index 27b8e5d0..892ef63a 100644 --- a/core/testdata/format/JavaSupertype.html +++ b/core/testdata/format/JavaSupertype.html @@ -7,7 +7,7 @@ <a href="../../index.html">test</a> / <a href="../index.html">JavaSupertype</a> / <a href="./index.html">Bar</a><br/> <br/> <h1>Bar</h1> -<code><span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Bar</span> <span class="symbol">:</span> <a href="../-foo/index.html"><span class="identifier">Foo</span></a></code> +<code><span class="keyword">open</span> <span class="keyword">class </span><span class="identifier">Bar</span> <span class="symbol">:</span> <a href="../-foo/index.html"><span class="identifier">JavaSupertype.Foo</span></a></code> <h3>Constructors</h3> <table> <tbody> @@ -28,7 +28,7 @@ <p><a href="return-foo.html">returnFoo</a></p> </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" id="JavaSupertype.Bar$returnFoo(JavaSupertype.Foo)/foo">foo</span><span class="symbol">:</span> <a href="../-foo/index.html"><span class="identifier">Foo</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="../-foo/index.html"><span class="identifier">Foo</span></a></code></td> +<code><span class="keyword">open</span> <span class="keyword">fun </span><span class="identifier">returnFoo</span><span class="symbol">(</span><span class="identifier" id="JavaSupertype.Bar$returnFoo(JavaSupertype.Foo)/foo">foo</span><span class="symbol">:</span> <a href="../-foo/index.html"><span class="identifier">JavaSupertype.Foo</span></a><span class="symbol">!</span><span class="symbol">)</span><span class="symbol">: </span><a href="../-foo/index.html"><span class="identifier">JavaSupertype.Foo</span></a><span class="symbol">!</span></code></td> </tr> </tbody> </table> diff --git a/core/testdata/format/enumRef.md b/core/testdata/format/enumRef.md index 8b2a6650..f5f5a3e0 100644 --- a/core/testdata/format/enumRef.md +++ b/core/testdata/format/enumRef.md @@ -4,5 +4,5 @@ `fun f(): Unit` -[java.math.RoundingMode.UP](http://docs.oracle.com/javase/6/docs/api/java/math/RoundingMode.html#UP) +[java.math.RoundingMode.UP](https://docs.oracle.com/javase/6/docs/api/java/math/RoundingMode.html#UP) diff --git a/core/testdata/format/externalReferenceLink.kt b/core/testdata/format/externalReferenceLink.kt index 4ca0ee21..775b2e66 100644 --- a/core/testdata/format/externalReferenceLink.kt +++ b/core/testdata/format/externalReferenceLink.kt @@ -3,7 +3,7 @@ * * Sure, it is [example.com] * - * [example.com]: http://example.com + * [example.com]: https://example.com */ fun a() { diff --git a/core/testdata/format/externalReferenceLink.md b/core/testdata/format/externalReferenceLink.md index 38ffde78..3565d9aa 100644 --- a/core/testdata/format/externalReferenceLink.md +++ b/core/testdata/format/externalReferenceLink.md @@ -4,7 +4,7 @@ `fun a(): Unit` -It is link to [example site](http://example.com) +It is link to [example site](https://example.com) -Sure, it is [example.com](http://example.com) +Sure, it is [example.com](https://example.com) diff --git a/core/testdata/format/inheritedLink.md b/core/testdata/format/inheritedLink.md index e5af326c..aec07a75 100644 --- a/core/testdata/format/inheritedLink.md +++ b/core/testdata/format/inheritedLink.md @@ -13,5 +13,5 @@ Overrides [Foo.sayHello](../../p1/-foo/say-hello.md) -Says hello - [LinkedList](http://docs.oracle.com/javase/6/docs/api/java/util/LinkedList.html). +Says hello - [LinkedList](https://docs.oracle.com/javase/6/docs/api/java/util/LinkedList.html). diff --git a/core/testdata/format/inlineSuspendFunction.kt b/core/testdata/format/inlineSuspendFunction.kt new file mode 100644 index 00000000..f2009eff --- /dev/null +++ b/core/testdata/format/inlineSuspendFunction.kt @@ -0,0 +1,6 @@ +/** + * returns 1 + */ +inline suspend fun foo(): Int { + 1 +} diff --git a/core/testdata/format/inlineSuspendFunction.md b/core/testdata/format/inlineSuspendFunction.md new file mode 100644 index 00000000..946463f7 --- /dev/null +++ b/core/testdata/format/inlineSuspendFunction.md @@ -0,0 +1,8 @@ +[test](index.md) / [foo](./foo.md) + +# foo + +`suspend inline fun foo(): Int` + +returns 1 + diff --git a/core/testdata/format/javaCodeInParam.java b/core/testdata/format/javaCodeInParam.java index 73025fcc..0d1607ba 100644 --- a/core/testdata/format/javaCodeInParam.java +++ b/core/testdata/format/javaCodeInParam.java @@ -1,5 +1,7 @@ -/** - * @param T this is {@code some code} and other text - */ -class C<T> { +class C { + + /** + * @param par this is {@code some code} and other text + */ + public void withParam(String par) {} } diff --git a/core/testdata/format/javaCodeInParam.md b/core/testdata/format/javaCodeInParam.md index 319c6d87..566b176d 100644 --- a/core/testdata/format/javaCodeInParam.md +++ b/core/testdata/format/javaCodeInParam.md @@ -1,14 +1,9 @@ -[test](../index.md) / [C](./index.md) +[test](../index.md) / [C](index.md) / [withParam](./with-param.md) -# C +# withParam -`protected open class C<T : Any>` +`open fun withParam(par: String!): Unit` ### Parameters -`T` - this is `some code` and other text - -### Constructors - -| [<init>](-init-.md) | `C()` | - +`par` - String!: this is `some code` and other text
\ No newline at end of file diff --git a/core/testdata/format/javaCodeLiteralTags.md b/core/testdata/format/javaCodeLiteralTags.md index b36be04d..c2c58047 100644 --- a/core/testdata/format/javaCodeLiteralTags.md +++ b/core/testdata/format/javaCodeLiteralTags.md @@ -12,5 +12,5 @@ A<B>C ### Constructors -| [<init>](-init-.md) | `C()`<br>`A<B>C` <br>A<B>C | +| [<init>](-init-.md) | `C()`<br>`A<B>C` | diff --git a/core/testdata/format/javadocHtml.java b/core/testdata/format/javadocHtml.java index 622116b2..9e77402e 100644 --- a/core/testdata/format/javadocHtml.java +++ b/core/testdata/format/javadocHtml.java @@ -9,6 +9,18 @@ * <code>Code</code> * <pre>Block code</pre> * <ul><li>List Item</li></ul> + * <pre> + * with( some ) { + * multi = lines + * sample() + * } + * </pre> + * <pre> + * {@code + * with (some) { <code> } + * } + * </pre> + * */ public class C { } diff --git a/core/testdata/format/javadocHtml.md b/core/testdata/format/javadocHtml.md index a3c1baff..77f6c829 100644 --- a/core/testdata/format/javadocHtml.md +++ b/core/testdata/format/javadocHtml.md @@ -16,13 +16,23 @@ Block code * List Item -### Constructors -| [<init>](-init-.md) | `C()`<br>**Bold** **Strong** *Italic* *Emphasized* <br>Paragraph ~~Strikethrough~~ ~~Deleted~~ `Code` +``` + + with( some ) { + multi = lines + sample() + } + ``` + + ``` -Block code<br>``` -<br> -* List Item -<br> | +with (some) { <code> } + + ``` + +### Constructors + +| [<init>](-init-.md) | `C()`<br>**Bold** **Strong** *Italic* *Emphasized* | diff --git a/core/testdata/format/jdkLinks.md b/core/testdata/format/jdkLinks.md index 7498171d..c3a5fbf4 100644 --- a/core/testdata/format/jdkLinks.md +++ b/core/testdata/format/jdkLinks.md @@ -2,13 +2,13 @@ # C -`class C : `[`ClassLoader`](http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) +`class C : `[`ClassLoader`](https://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) -This is a [ClassLoader](http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) and I can get its [ClassLoader.getResource](http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)) +This is a [ClassLoader](https://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) and I can get its [ClassLoader.getResource](https://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)) -You can print something to [java.lang.System.out](http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#out) now! +You can print something to [java.lang.System.out](https://docs.oracle.com/javase/6/docs/api/java/lang/System.html#out) now! ### Constructors -| [<init>](-init-.md) | `C()`<br>This is a [ClassLoader](http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) and I can get its [ClassLoader.getResource](http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)) | +| [<init>](-init-.md) | `C()`<br>This is a [ClassLoader](https://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html) and I can get its [ClassLoader.getResource](https://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)) | diff --git a/core/testdata/format/markdownInLinks.html b/core/testdata/format/markdownInLinks.html index 596cca73..f0bb475e 100644 --- a/core/testdata/format/markdownInLinks.html +++ b/core/testdata/format/markdownInLinks.html @@ -9,6 +9,6 @@ <h1>foo</h1> <a name="$foo()"></a> <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> -<p><a href="http://www.ibm.com">a<strong>b</strong><strong>d</strong> kas </a></p> +<p><a href="https://www.ibm.com">a<strong>b</strong><strong>d</strong> kas </a></p> </BODY> </HTML> diff --git a/core/testdata/format/markdownInLinks.kt b/core/testdata/format/markdownInLinks.kt index 67b6311f..380727ee 100644 --- a/core/testdata/format/markdownInLinks.kt +++ b/core/testdata/format/markdownInLinks.kt @@ -1,4 +1,4 @@ /** - * [a**b**__d__ kas ](http://www.ibm.com) + * [a**b**__d__ kas ](https://www.ibm.com) */ fun foo() {} diff --git a/core/testdata/format/nullableTypeParameterFunction.kt b/core/testdata/format/nullableTypeParameterFunction.kt new file mode 100644 index 00000000..01805a7b --- /dev/null +++ b/core/testdata/format/nullableTypeParameterFunction.kt @@ -0,0 +1,8 @@ +class Bar<T> { + val dataList = mutableListOf<T>() + + open fun checkElement( + elem: T, + addFunc: ((elem: T) -> Unit)? = { dataList.add(it) } + ): Int = 1 +}
\ No newline at end of file diff --git a/core/testdata/format/nullableTypeParameterFunction.md b/core/testdata/format/nullableTypeParameterFunction.md new file mode 100644 index 00000000..5764007b --- /dev/null +++ b/core/testdata/format/nullableTypeParameterFunction.md @@ -0,0 +1,18 @@ +[test](../index.md) / [Bar](./index.md) + +# Bar + +`class Bar<T>` + +### Constructors + +| [<init>](-init-.md) | `Bar()` | + +### Properties + +| [dataList](data-list.md) | `val dataList: MutableList<`[`T`](index.md#T)`>` | + +### Functions + +| [checkElement](check-element.md) | `fun checkElement(elem: `[`T`](index.md#T)`, addFunc: ((elem: `[`T`](index.md#T)`) -> Unit)? = { dataList.add(it) }): Int` | + diff --git a/core/testdata/format/renderFunctionalTypeInParenthesisWhenItIsReceiver.md b/core/testdata/format/renderFunctionalTypeInParenthesisWhenItIsReceiver.md index ad632fef..4b5f3a64 100644 --- a/core/testdata/format/renderFunctionalTypeInParenthesisWhenItIsReceiver.md +++ b/core/testdata/format/renderFunctionalTypeInParenthesisWhenItIsReceiver.md @@ -1,6 +1,6 @@ -[test](../index.md) / [kotlin.SuspendFunction0](./index.md) +[test](../index.md) / [kotlin.coroutines.SuspendFunction0](./index.md) -### Extensions for kotlin.SuspendFunction0 +### Extensions for kotlin.coroutines.SuspendFunction0 | [foo](foo.md) | `fun (suspend () -> Unit).foo(): Unit` | diff --git a/core/testdata/format/suspendInlineFunction.kt b/core/testdata/format/suspendInlineFunction.kt new file mode 100644 index 00000000..8af0d11a --- /dev/null +++ b/core/testdata/format/suspendInlineFunction.kt @@ -0,0 +1,6 @@ +/** + * returns 1 + */ +suspend inline fun foo(): Int { + 1 +} diff --git a/core/testdata/format/suspendInlineFunction.md b/core/testdata/format/suspendInlineFunction.md new file mode 100644 index 00000000..946463f7 --- /dev/null +++ b/core/testdata/format/suspendInlineFunction.md @@ -0,0 +1,8 @@ +[test](index.md) / [foo](./foo.md) + +# foo + +`suspend inline fun foo(): Int` + +returns 1 + diff --git a/core/testdata/functions/inlineSuspendFunction.kt b/core/testdata/functions/inlineSuspendFunction.kt new file mode 100644 index 00000000..54032ccf --- /dev/null +++ b/core/testdata/functions/inlineSuspendFunction.kt @@ -0,0 +1,2 @@ +inline suspend fun f() { +} diff --git a/core/testdata/functions/suspendFunction.kt b/core/testdata/functions/suspendFunction.kt new file mode 100644 index 00000000..49ecca2a --- /dev/null +++ b/core/testdata/functions/suspendFunction.kt @@ -0,0 +1,2 @@ +suspend fun f() { +} diff --git a/core/testdata/functions/suspendInlineFunction.kt b/core/testdata/functions/suspendInlineFunction.kt new file mode 100644 index 00000000..15a9018f --- /dev/null +++ b/core/testdata/functions/suspendInlineFunction.kt @@ -0,0 +1,2 @@ +suspend inline fun f() { +} diff --git a/core/testdata/java/constants.java b/core/testdata/java/constants.java new file mode 100644 index 00000000..26f16639 --- /dev/null +++ b/core/testdata/java/constants.java @@ -0,0 +1,5 @@ +public class Constants { + public static final String constStr = "some value"; + public static final Object nullConst = null; + public static final String refConst = constStr; +}
\ No newline at end of file diff --git a/core/testdata/javadoc/argumentReference.kt b/core/testdata/javadoc/argumentReference.kt new file mode 100644 index 00000000..ac3104e9 --- /dev/null +++ b/core/testdata/javadoc/argumentReference.kt @@ -0,0 +1,4 @@ +/** + * [error] + */ +fun argNamedError(error: String) {}
\ No newline at end of file diff --git a/core/testdata/javadoc/constructorParameters.kt b/core/testdata/javadoc/constructorParameters.kt new file mode 100644 index 00000000..c29ae912 --- /dev/null +++ b/core/testdata/javadoc/constructorParameters.kt @@ -0,0 +1,14 @@ +package bar + +/** + * Just a fruit + * + * @param weight in grams + * @param ranking quality from 0 to 10, where 10 is best + * @param color yellow is default + */ +class Banana ( + private val weight: Double, + private val ranking: Int, + color: String = "yellow" +)
\ No newline at end of file diff --git a/core/testdata/javadoc/defaultNoArgConstructor.kt b/core/testdata/javadoc/defaultNoArgConstructor.kt new file mode 100644 index 00000000..3a6d04a5 --- /dev/null +++ b/core/testdata/javadoc/defaultNoArgConstructor.kt @@ -0,0 +1,12 @@ +package foo + +/** + * Description + * + * @constructor print peach + */ +class Peach { + init { + println("peach") + } +}
\ No newline at end of file diff --git a/core/testdata/javadoc/deprecated.java b/core/testdata/javadoc/deprecated.java new file mode 100644 index 00000000..5a6cdd77 --- /dev/null +++ b/core/testdata/javadoc/deprecated.java @@ -0,0 +1,28 @@ +package bar; + +/** + * Just a fruit + */ +public class Banana { + private Double weight; + + /** + * Returns weight + * + * @return weight + * @deprecated + */ + public Double getWeight() { + return weight; + } + + /** + * Sets weight + * + * @param weight in grams + * @deprecated with message + */ + public void setWeight(Double weight) { + this.weight = weight; + } +}
\ No newline at end of file diff --git a/core/testdata/javadoc/noArgConstructor.kt b/core/testdata/javadoc/noArgConstructor.kt new file mode 100644 index 00000000..25e5548c --- /dev/null +++ b/core/testdata/javadoc/noArgConstructor.kt @@ -0,0 +1,12 @@ +package foo + +/** + * Description + * + * @constructor print plum + */ +class Plum() { + init { + println("plum") + } +}
\ No newline at end of file diff --git a/core/testdata/javadoc/vararg.kt b/core/testdata/javadoc/vararg.kt new file mode 100644 index 00000000..aa6c26d7 --- /dev/null +++ b/core/testdata/javadoc/vararg.kt @@ -0,0 +1,3 @@ +fun vararg(a: String, vararg b: Int) {} + +fun varargInMiddle(a: String, vararg b: Int, c: Short) {}
\ No newline at end of file diff --git a/core/testdata/javadoc/visibilityModifiers.kt b/core/testdata/javadoc/visibilityModifiers.kt new file mode 100644 index 00000000..e48e7f62 --- /dev/null +++ b/core/testdata/javadoc/visibilityModifiers.kt @@ -0,0 +1,15 @@ +package foo + +abstract class Apple { + protected var name: String = "foo" + internal var weight: Int = 180 + var rating: Int = 10 + private var color: String = "red" + + companion object { + @JvmStatic + val code : Int = 123456 + } + + +}
\ No newline at end of file diff --git a/core/testdata/markdown/spec.txt b/core/testdata/markdown/spec.txt index fce87924..916bdd89 100644 --- a/core/testdata/markdown/spec.txt +++ b/core/testdata/markdown/spec.txt @@ -23,7 +23,7 @@ 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) +syntax](https://daringfireball.net/projects/markdown/syntax) does not specify the syntax unambiguously. Here are some examples of questions it does not answer: @@ -34,7 +34,7 @@ questions it does not answer: 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).) + Gruber](https://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, @@ -42,7 +42,7 @@ questions it does not answer: 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).) + lines](https://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 @@ -75,7 +75,7 @@ questions it does not answer: ``` (There are some relevant comments by John Gruber - [here](http://article.gmane.org/gmane.text.markdown.general/2554).) + [here](https://article.gmane.org/gmane.text.markdown.general/2554).) 5. Can list markers be indented? Can ordered list markers be right-aligned? @@ -509,7 +509,7 @@ More than six `#` characters is not a header: 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 +implementation](https://www.aaronsw.com/2002/atx/atx.py), and it helps prevent things like the following from being parsed as headers: . @@ -3686,9 +3686,9 @@ raw HTML: . . -<http://google.com?find=\*> +<https://google.com?find=\*> . -<p><a href="http://google.com?find=%5C*">http://google.com?find=\*</a></p> +<p><a href="https://google.com?find=%5C*">https://google.com?find=\*</a></p> . . @@ -3736,7 +3736,7 @@ and simplifies the job of implementations targetting other languages, as these w 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) ++ any of the valid HTML5 entity names + `;`. The [following document](https://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 @@ -3955,9 +3955,9 @@ And this is not parsed as a link: But this is a link: . -<http://foo.bar.`baz>` +<https://foo.bar.`baz>` . -<p><a href="http://foo.bar.%60baz">http://foo.bar.`baz</a>`</p> +<p><a href="https://foo.bar.%60baz">https://foo.bar.`baz</a>`</p> . And this is an HTML tag: @@ -3986,7 +3986,7 @@ we just have literal backticks: ## Emphasis and strong emphasis John Gruber's original [Markdown syntax -description](http://daringfireball.net/projects/markdown/syntax#em) says: +description](https://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 @@ -4229,15 +4229,15 @@ _a `_`_ . . -**a<http://foo.bar?q=**> +**a<https://foo.bar?q=**> . -<p>**a<a href="http://foo.bar?q=**">http://foo.bar?q=**</a></p> +<p>**a<a href="https://foo.bar?q=**">https://foo.bar?q=**</a></p> . . -__a<http://foo.bar?q=__> +__a<https://foo.bar?q=__> . -<p>__a<a href="http://foo.bar?q=__">http://foo.bar?q=__</a></p> +<p>__a<a href="https://foo.bar?q=__">https://foo.bar?q=__</a></p> . This is not emphasis, because the opening delimiter is @@ -5455,15 +5455,15 @@ soap.beep`, `soap.beeps`, `tag`, `tel`, `telnet`, `tftp`, `thismessage`, Here are some valid autolinks: . -<http://foo.bar.baz> +<https://foo.bar.baz> . -<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p> +<p><a href="https://foo.bar.baz">https://foo.bar.baz</a></p> . . -<http://foo.bar.baz?q=hello&id=22&boolean> +<https://foo.bar.baz?q=hello&id=22&boolean> . -<p><a href="http://foo.bar.baz?q=hello&id=22&boolean">http://foo.bar.baz?q=hello&id=22&boolean</a></p> +<p><a href="https://foo.bar.baz?q=hello&id=22&boolean">https://foo.bar.baz?q=hello&id=22&boolean</a></p> . . @@ -5483,9 +5483,9 @@ Uppercase is also fine: Spaces are not allowed in autolinks: . -<http://foo.bar/baz bim> +<https://foo.bar/baz bim> . -<p><http://foo.bar/baz bim></p> +<p><https://foo.bar/baz bim></p> . An [email autolink](#email-autolink) <a id="email-autolink"></a> @@ -5496,7 +5496,7 @@ 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): +spec](https://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])?)*$/ @@ -5530,9 +5530,9 @@ These are not autolinks: . . -< http://foo.bar > +< https://foo.bar > . -<p>< http://foo.bar ></p> +<p>< https://foo.bar ></p> . . @@ -5548,9 +5548,9 @@ These are not autolinks: . . -http://google.com +https://google.com . -<p>http://google.com</p> +<p>https://google.com</p> . . diff --git a/core/testdata/packagedocs/referenceLinks.kotlin.md b/core/testdata/packagedocs/referenceLinks.kotlin.md index ac7e4b48..f7b1edad 100644 --- a/core/testdata/packagedocs/referenceLinks.kotlin.md +++ b/core/testdata/packagedocs/referenceLinks.kotlin.md @@ -1,7 +1,6 @@ Core functions and types -See [ref](http://example.com) -Also, [example](http://example.com) +See [ref](https://example.com) +Also, [example](https://example.com) -
\ No newline at end of file diff --git a/core/testdata/packagedocs/referenceLinks.md b/core/testdata/packagedocs/referenceLinks.md index 7583ee9d..177dea0c 100644 --- a/core/testdata/packagedocs/referenceLinks.md +++ b/core/testdata/packagedocs/referenceLinks.md @@ -14,4 +14,4 @@ See [ref] Also, [example][ref] <!-- Refs --> -[ref]: http://example.com +[ref]: https://example.com diff --git a/core/testdata/packagedocs/referenceLinks.module.md b/core/testdata/packagedocs/referenceLinks.module.md index ddbdbe2f..08372175 100644 --- a/core/testdata/packagedocs/referenceLinks.module.md +++ b/core/testdata/packagedocs/referenceLinks.module.md @@ -4,6 +4,6 @@ The Kotlin standard library is a set of functions and types implementing idiomatic patterns when working with collections, text and files. -See [ref](http://example.com) -Also, [example](http://example.com) +See [ref](https://example.com) +Also, [example](https://example.com) diff --git a/core/testdata/properties/annotatedProperty.kt b/core/testdata/properties/annotatedProperty.kt index 8990af29..3c12691b 100644 --- a/core/testdata/properties/annotatedProperty.kt +++ b/core/testdata/properties/annotatedProperty.kt @@ -1 +1 @@ -@Volatile var property = "test"
\ No newline at end of file +@Strictfp var property = "test"
\ No newline at end of file diff --git a/core/testdata/sourceLinks/dummy.kt b/core/testdata/sourceLinks/dummy.kt new file mode 100644 index 00000000..cbaffe7c --- /dev/null +++ b/core/testdata/sourceLinks/dummy.kt @@ -0,0 +1,6 @@ +/** + * Some doc. + */ +fun foo(){ + +} diff --git a/gradle.properties b/gradle.properties index 0e98fc46..9436e38b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ -dokka_version=0.9.17-SNAPSHOT +dokka_version=0.9.18-SNAPSHOT dokka_publication_channel=dokka #Kotlin compiler and plugin -bundled_kotlin_compiler_version=1.2.40-dev-529 +bundled_kotlin_compiler_version=1.3.20-dev-564 kotlin_version=1.2.21 kotlin_for_gradle_runtime_version=1.1.60 @@ -13,7 +13,4 @@ maven_version=3.5.0 maven_archiver_version=2.5 plexus_utils_version=3.0.22 plexus_archiver_version=3.4 -maven_plugin_tools_version=3.5 - -#For CI -mvn=mvn
\ No newline at end of file +maven_plugin_tools_version=3.5.2 diff --git a/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt b/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt index 90e5b5fc..69d19944 100644 --- a/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt +++ b/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt @@ -39,8 +39,10 @@ interface DokkaConfiguration { val languageVersion: String? val apiVersion: String? val noStdlibLink: Boolean + val noJdkLink: Boolean val cacheRoot: String? val suppressedFiles: List<String> + val collectInheritedExtensionsFromLibraries: Boolean interface SourceRoot { val path: String @@ -82,29 +84,31 @@ interface DokkaConfiguration { } data class SerializeOnlyDokkaConfiguration( - override val moduleName: String, - override val classpath: List<String>, - override val sourceRoots: List<DokkaConfiguration.SourceRoot>, - override val samples: List<String>, - override val includes: List<String>, - override val outputDir: String, - override val format: String, - override val includeNonPublic: Boolean, - override val includeRootPackage: Boolean, - override val reportUndocumented: Boolean, - override val skipEmptyPackages: Boolean, - override val skipDeprecated: Boolean, - override val jdkVersion: Int, - override val generateIndexPages: Boolean, - override val sourceLinks: List<DokkaConfiguration.SourceLinkDefinition>, - override val impliedPlatforms: List<String>, - override val perPackageOptions: List<DokkaConfiguration.PackageOptions>, - override val externalDocumentationLinks: List<DokkaConfiguration.ExternalDocumentationLink>, - override val noStdlibLink: Boolean, - override val cacheRoot: String?, - override val suppressedFiles: List<String>, - override val languageVersion: String?, - override val apiVersion: String? + override val moduleName: String, + override val classpath: List<String>, + override val sourceRoots: List<DokkaConfiguration.SourceRoot>, + override val samples: List<String>, + override val includes: List<String>, + override val outputDir: String, + override val format: String, + override val includeNonPublic: Boolean, + override val includeRootPackage: Boolean, + override val reportUndocumented: Boolean, + override val skipEmptyPackages: Boolean, + override val skipDeprecated: Boolean, + override val jdkVersion: Int, + override val generateIndexPages: Boolean, + override val sourceLinks: List<DokkaConfiguration.SourceLinkDefinition>, + override val impliedPlatforms: List<String>, + override val perPackageOptions: List<DokkaConfiguration.PackageOptions>, + override val externalDocumentationLinks: List<DokkaConfiguration.ExternalDocumentationLink>, + override val noStdlibLink: Boolean, + override val noJdkLink: Boolean, + override val cacheRoot: String?, + override val suppressedFiles: List<String>, + override val languageVersion: String?, + override val apiVersion: String?, + override val collectInheritedExtensionsFromLibraries: Boolean ) : DokkaConfiguration diff --git a/runners/android-gradle-plugin/build.gradle b/runners/android-gradle-plugin/build.gradle index 72d1be9e..28b0cbb9 100644 --- a/runners/android-gradle-plugin/build.gradle +++ b/runners/android-gradle-plugin/build.gradle @@ -76,7 +76,7 @@ artifacts { } pluginBundle { - website = 'http://www.kotlinlang.org/' + website = 'https://www.kotlinlang.org/' vcsUrl = 'https://github.com/kotlin/dokka.git' description = 'Dokka, the Kotlin documentation tool' tags = ['dokka', 'kotlin', 'kdoc', 'android'] diff --git a/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt b/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt index bd2e88c2..b1996da0 100644 --- a/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt +++ b/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt @@ -10,6 +10,7 @@ open class DokkaAndroidPlugin : Plugin<Project> { override fun apply(project: Project) { DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka-android.properties")) project.tasks.create("dokka", DokkaAndroidTask::class.java).apply { + dokkaRuntime = project.configurations.create("dokkaRuntime") moduleName = project.name outputDirectory = File(project.buildDir, "dokka").absolutePath } diff --git a/runners/ant/src/main/kotlin/ant/dokka.kt b/runners/ant/src/main/kotlin/ant/dokka.kt index d1b6bef5..b23328e0 100644 --- a/runners/ant/src/main/kotlin/ant/dokka.kt +++ b/runners/ant/src/main/kotlin/ant/dokka.kt @@ -40,6 +40,7 @@ class DokkaAntTask: Task() { var jdkVersion: Int = 6 var noStdlibLink: Boolean = false + var noJdkLink: Boolean = false var skipDeprecated: Boolean = false @@ -110,6 +111,10 @@ class DokkaAntTask: Task() { } val sourceLinks = antSourceLinks.map { val path = it.path ?: throw BuildException("'path' attribute of a <sourceLink> element is required") + if (path.contains("\\")) { + throw BuildException("'dir' attribute of a <sourceLink> - incorrect value, only Unix based path allowed.") + } + val url = it.url ?: throw BuildException("'url' attribute of a <sourceLink> element is required") SourceLinkDefinitionImpl(File(path).canonicalFile.absolutePath, url, it.lineSuffix) } @@ -131,6 +136,7 @@ class DokkaAntTask: Task() { perPackageOptions = antPackageOptions, externalDocumentationLinks = antExternalDocumentationLinks.map { it.build() }, noStdlibLink = noStdlibLink, + noJdkLink = noJdkLink, cacheRoot = cacheRoot, languageVersion = languageVersion, apiVersion = apiVersion diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index fe945ed3..f871f406 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -53,6 +53,9 @@ class DokkaArguments { @set:Argument(value = "noStdlibLink", description = "Disable documentation link to stdlib") var noStdlibLink: Boolean = false + @set:Argument(value = "noJdkLink", description = "Disable documentation link to jdk") + var noJdkLink: Boolean = false + @set:Argument(value = "cacheRoot", description = "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled") var cacheRoot: String? = null @@ -62,6 +65,9 @@ class DokkaArguments { @set:Argument(value = "apiVersion", description = "Kotlin Api Version to pass to Kotlin Analysis") var apiVersion: String? = null + @set:Argument(value = "collectInheritedExtensionsFromLibraries", description = "Search for applicable extensions in libraries") + var collectInheritedExtensionsFromLibraries: Boolean = false + } @@ -106,18 +112,20 @@ object MainKt { val classPath = arguments.classpath.split(File.pathSeparatorChar).toList() val documentationOptions = DocumentationOptions( - arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' }, - arguments.outputFormat, - skipDeprecated = arguments.nodeprecated, - sourceLinks = sourceLinks, - impliedPlatforms = arguments.impliedPlatforms.split(','), - perPackageOptions = parsePerPackageOptions(arguments.packageOptions), - jdkVersion = arguments.jdkVersion, - externalDocumentationLinks = parseLinks(arguments.links), - noStdlibLink = arguments.noStdlibLink, - cacheRoot = arguments.cacheRoot, - languageVersion = arguments.languageVersion, - apiVersion = arguments.apiVersion + arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' }, + arguments.outputFormat, + skipDeprecated = arguments.nodeprecated, + sourceLinks = sourceLinks, + impliedPlatforms = arguments.impliedPlatforms.split(','), + perPackageOptions = parsePerPackageOptions(arguments.packageOptions), + jdkVersion = arguments.jdkVersion, + externalDocumentationLinks = parseLinks(arguments.links), + noStdlibLink = arguments.noStdlibLink, + cacheRoot = arguments.cacheRoot, + languageVersion = arguments.languageVersion, + apiVersion = arguments.apiVersion, + collectInheritedExtensionsFromLibraries = arguments.collectInheritedExtensionsFromLibraries, + noJdkLink = arguments.noJdkLink ) val generator = DokkaGenerator( diff --git a/runners/gradle-integration-tests/build.gradle b/runners/gradle-integration-tests/build.gradle index a681c82e..297a175a 100644 --- a/runners/gradle-integration-tests/build.gradle +++ b/runners/gradle-integration-tests/build.gradle @@ -56,4 +56,5 @@ testClasses.dependsOn createClasspathManifest test { systemProperty "android.licenses.overwrite", project.findProperty("android.licenses.overwrite") ?: "" + inputs.dir(file('testData')) }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/TypeSafeConfigurationTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/TypeSafeConfigurationTest.kt new file mode 100644 index 00000000..7b179e92 --- /dev/null +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/TypeSafeConfigurationTest.kt @@ -0,0 +1,36 @@ +package org.jetbrains.dokka.gradle + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class TypeSafeConfigurationTest(private val testCase: TestCase) : AbstractDokkaGradleTest() { + + data class TestCase(val gradleVersion: String, val kotlinVersion: String) { + override fun toString(): String = "Gradle $gradleVersion and Kotlin $kotlinVersion" + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun testCases() = listOf( + TestCase("4.0", "1.1.2"), + TestCase("4.5", "1.2.20"), + TestCase("4.10.1", "1.2.60") + ) + } + + @Test + fun test() { + + testDataFolder.resolve("typeSafeConfiguration").toFile() + .copyRecursively(testProjectDir.root) + + configure( + testCase.gradleVersion, + testCase.kotlinVersion, + arguments = arrayOf("help", "-s") + ).build() + } +} diff --git a/runners/gradle-integration-tests/testData/androidApp/build.gradle b/runners/gradle-integration-tests/testData/androidApp/build.gradle index 59477b52..35356b90 100644 --- a/runners/gradle-integration-tests/testData/androidApp/build.gradle +++ b/runners/gradle-integration-tests/testData/androidApp/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() jcenter() maven { url 'https://maven.google.com' } - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -15,7 +15,7 @@ allprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } } diff --git a/runners/gradle-integration-tests/testData/androidApp/fileTree.txt b/runners/gradle-integration-tests/testData/androidApp/fileTree.txt index 3827b69e..f66c79e3 100644 --- a/runners/gradle-integration-tests/testData/androidApp/fileTree.txt +++ b/runners/gradle-integration-tests/testData/androidApp/fileTree.txt @@ -9,6 +9,7 @@ -init-.html index.html on-create-options-menu.html + on-create.html -kotlin-activity/ -init-.html index.html diff --git a/runners/gradle-integration-tests/testData/androidAppJavadoc/build.gradle b/runners/gradle-integration-tests/testData/androidAppJavadoc/build.gradle index 59477b52..35356b90 100644 --- a/runners/gradle-integration-tests/testData/androidAppJavadoc/build.gradle +++ b/runners/gradle-integration-tests/testData/androidAppJavadoc/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() jcenter() maven { url 'https://maven.google.com' } - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -15,7 +15,7 @@ allprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } } diff --git a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/build.gradle b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/build.gradle index 59477b52..35356b90 100644 --- a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/build.gradle +++ b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() jcenter() maven { url 'https://maven.google.com' } - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -15,7 +15,7 @@ allprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } } diff --git a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/fileTree.txt b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/fileTree.txt index 5e969d8b..77b563b2 100644 --- a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/fileTree.txt +++ b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/fileTree.txt @@ -10,6 +10,7 @@ -init-.html index.html on-create-options-menu.html + on-create.html -kotlin-activity/ -init-.html index.html @@ -35,6 +36,7 @@ -init-.html index.html on-create-options-menu.html + on-create.html -kotlin-activity/ -init-.html index.html diff --git a/runners/gradle-integration-tests/testData/basic/build.gradle b/runners/gradle-integration-tests/testData/basic/build.gradle index 4a259f50..a3116751 100644 --- a/runners/gradle-integration-tests/testData/basic/build.gradle +++ b/runners/gradle-integration-tests/testData/basic/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -21,7 +21,7 @@ repositories { mavenCentral() jcenter() maven { - url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" + url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" diff --git a/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle b/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle index 68d93e30..4f561472 100644 --- a/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle +++ b/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle @@ -7,7 +7,7 @@ subprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -17,7 +17,7 @@ subprojects { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } } diff --git a/runners/gradle-integration-tests/testData/sourcesChange/build.gradle b/runners/gradle-integration-tests/testData/sourcesChange/build.gradle index bc20e1cf..4627e8ef 100644 --- a/runners/gradle-integration-tests/testData/sourcesChange/build.gradle +++ b/runners/gradle-integration-tests/testData/sourcesChange/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories { mavenCentral() jcenter() - maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } } dependencies { @@ -21,7 +21,7 @@ repositories { mavenCentral() jcenter() maven { - url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" + url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } maven { url "https://dl.bintray.com/kotlin/kotlin-dev" diff --git a/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle b/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle new file mode 100644 index 00000000..327cead8 --- /dev/null +++ b/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle @@ -0,0 +1,84 @@ +import org.jetbrains.dokka.* +import org.jetbrains.dokka.gradle.* +import org.jetbrains.kotlin.gradle.tasks.* + +import groovy.transform.CompileStatic +import java.util.concurrent.Callable + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$test_kotlin_version" + } +} + +plugins { + id 'org.jetbrains.dokka' +} + +apply plugin: 'kotlin' + +@CompileStatic +def configureDokkaTypeSafely(DokkaTask dokka) { + dokka.with { + moduleName = "some String" + outputFormat = "some String" + outputDirectory = "some String" + classpath = Collections.singleton(file("someClassDir")) + includes = Collections.emptyList() + linkMappings = new ArrayList<LinkMapping>() + samples = Collections.emptyList() + jdkVersion = 6 + sourceDirs = Collections.<File>emptyList() + sourceRoots = new ArrayList<SourceRoot>() + dokkaFatJar = file("some File") + includeNonPublic = false + skipDeprecated = false + skipEmptyPackages = true + reportUndocumented = true + perPackageOptions = new ArrayList<PackageOptions>() + impliedPlatforms = Collections.<String>emptyList() + externalDocumentationLinks = new ArrayList<DokkaConfiguration.ExternalDocumentationLink>() + noStdlibLink = false + cacheRoot = null as String + languageVersion = null as String + apiVersion = null as String + kotlinTasks(new Callable<List<Object>>() { + @Override + List<Object> call() { + return defaultKotlinTasks() + } + }) + linkMapping(new Action<LinkMapping>() { + @Override + void execute(LinkMapping mapping) { + mapping.dir = "some String" + mapping.url = "some String" + } + }) + sourceRoot(new Action<SourceRoot>() { + @Override + void execute(SourceRoot sourceRoot) { + sourceRoot.path = "some String" + } + }) + packageOptions(new Action<PackageOptions>() { + @Override + void execute(PackageOptions packageOptions) { + packageOptions.prefix = "some String" + } + }) + externalDocumentationLink(new Action<DokkaConfiguration.ExternalDocumentationLink.Builder>() { + @Override + void execute(DokkaConfiguration.ExternalDocumentationLink.Builder builder) { + builder.url = uri("some URI").toURL() + } + }) + } +} + +project.tasks.withType(DokkaTask) { dokka -> + configureDokkaTypeSafely(dokka) +} diff --git a/runners/gradle-integration-tests/testData/typeSafeConfiguration/settings.gradle b/runners/gradle-integration-tests/testData/typeSafeConfiguration/settings.gradle new file mode 100644 index 00000000..be82e328 --- /dev/null +++ b/runners/gradle-integration-tests/testData/typeSafeConfiguration/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "type-safe-configuration"
\ No newline at end of file diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle index 661d432b..8e59a7be 100644 --- a/runners/gradle-plugin/build.gradle +++ b/runners/gradle-plugin/build.gradle @@ -74,7 +74,7 @@ artifacts { } pluginBundle { - website = 'http://www.kotlinlang.org/' + website = 'https://www.kotlinlang.org/' vcsUrl = 'https://github.com/kotlin/dokka.git' description = 'Dokka, the Kotlin documentation tool' tags = ['dokka', 'kotlin', 'kdoc'] diff --git a/runners/gradle-plugin/src/main/kotlin/main.kt b/runners/gradle-plugin/src/main/kotlin/main.kt index 5f02cd0e..f4adc1c3 100644 --- a/runners/gradle-plugin/src/main/kotlin/main.kt +++ b/runners/gradle-plugin/src/main/kotlin/main.kt @@ -1,10 +1,12 @@ package org.jetbrains.dokka.gradle import groovy.lang.Closure +import org.gradle.api.Action import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import org.gradle.api.file.FileCollection import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.plugins.JavaPluginConvention @@ -30,6 +32,7 @@ open class DokkaPlugin : Plugin<Project> { override fun apply(project: Project) { DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties")) project.tasks.create("dokka", DokkaTask::class.java).apply { + dokkaRuntime = project.configurations.create("dokkaRuntime") moduleName = project.name outputDirectory = File(project.buildDir, "dokka").absolutePath } @@ -80,7 +83,7 @@ open class DokkaTask : DefaultTask() { @Input var outputFormat: String = "html" var outputDirectory: String = "" - + var dokkaRuntime: Configuration? = null @Deprecated("Going to be removed in 0.9.16, use classpath + sourceDirs instead if kotlinTasks is not suitable for you") @Input var processConfigurations: List<Any?> = emptyList() @@ -124,6 +127,9 @@ open class DokkaTask : DefaultTask() { @Input var noStdlibLink: Boolean = false + @Input + var noJdkLink: Boolean = false + @Optional @Input var cacheRoot: String? = null @@ -134,6 +140,9 @@ open class DokkaTask : DefaultTask() { @Optional @Input var apiVersion: String? = null + @Input + var collectInheritedExtensionsFromLibraries: Boolean = false + @get:Internal internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() } @@ -141,14 +150,17 @@ open class DokkaTask : DefaultTask() { private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() } private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() } + fun kotlinTasks(taskSupplier: Callable<List<Any>>) { + kotlinTasksConfigurator = { taskSupplier.call() } + } + fun kotlinTasks(closure: Closure<Any?>) { kotlinTasksConfigurator = { closure.call() as? List<Any?> } } - fun linkMapping(closure: Closure<Unit>) { + fun linkMapping(action: Action<LinkMapping>) { val mapping = LinkMapping() - closure.delegate = mapping - closure.call() + action.execute(mapping) if (mapping.path.isEmpty()) { throw IllegalArgumentException("Link mapping should have dir") @@ -160,33 +172,55 @@ open class DokkaTask : DefaultTask() { linkMappings.add(mapping) } - fun sourceRoot(closure: Closure<Unit>) { + fun linkMapping(closure: Closure<Unit>) { + linkMapping(Action { mapping -> + closure.delegate = mapping + closure.call() + }) + } + + fun sourceRoot(action: Action<SourceRoot>) { val sourceRoot = SourceRoot() - closure.delegate = sourceRoot - closure.call() + action.execute(sourceRoot) sourceRoots.add(sourceRoot) } - fun packageOptions(closure: Closure<Unit>) { + fun sourceRoot(closure: Closure<Unit>) { + sourceRoot(Action { sourceRoot -> + closure.delegate = sourceRoot + closure.call() + }) + } + + fun packageOptions(action: Action<PackageOptions>) { val packageOptions = PackageOptions() - closure.delegate = packageOptions - closure.call() + action.execute(packageOptions) perPackageOptions.add(packageOptions) } - fun externalDocumentationLink(closure: Closure<Unit>) { + fun packageOptions(closure: Closure<Unit>) { + packageOptions(Action { packageOptions -> + closure.delegate = packageOptions + closure.call() + }) + } + + fun externalDocumentationLink(action: Action<DokkaConfiguration.ExternalDocumentationLink.Builder>) { val builder = DokkaConfiguration.ExternalDocumentationLink.Builder() - closure.delegate = builder - closure.call() + action.execute(builder) externalDocumentationLinks.add(builder.build()) } - fun tryResolveFatJar(project: Project): File { + fun externalDocumentationLink(closure: Closure<Unit>) { + externalDocumentationLink(Action { builder -> + closure.delegate = builder + closure.call() + }) + } + + fun tryResolveFatJar(project: Project): Set<File> { return try { - val dependency = project.buildscript.dependencies.create(dokkaFatJar) - val configuration = project.buildscript.configurations.detachedConfiguration(dependency) - configuration.description = "Dokka main jar" - configuration.resolve().first() + dokkaRuntime!!.resolve() } catch (e: Exception) { project.parent?.let { tryResolveFatJar(it) } ?: throw e } @@ -194,11 +228,11 @@ open class DokkaTask : DefaultTask() { fun loadFatJar() { if (fatJarClassLoader == null) { - val fatjar = if (dokkaFatJar is File) - dokkaFatJar as File + val jars = if (dokkaFatJar is File) + setOf(dokkaFatJar as File) else tryResolveFatJar(project) - fatJarClassLoader = URLClassLoader(arrayOf(fatjar.toURI().toURL()), ClassLoader.getSystemClassLoader().parent) + fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent) } } @@ -263,6 +297,11 @@ open class DokkaTask : DefaultTask() { @TaskAction fun generate() { + if (dokkaRuntime == null){ + dokkaRuntime = project.configurations.getByName("dokkaRuntime") + } + + dokkaRuntime?.defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create(dokkaFatJar)) } val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false" System.setProperty(COLORS_ENABLED_PROPERTY, "false") try { @@ -287,29 +326,32 @@ open class DokkaTask : DefaultTask() { val bootstrapProxy: DokkaBootstrap = automagicTypedProxy(javaClass.classLoader, bootstrapInstance) val configuration = SerializeOnlyDokkaConfiguration( - moduleName, - fullClasspath.map { it.absolutePath }, - sourceRoots, - samples.filterNotNull().map { project.file(it).absolutePath }, - includes.filterNotNull().map { project.file(it).absolutePath }, - outputDirectory, - outputFormat, - includeNonPublic, - false, - reportUndocumented, - skipEmptyPackages, - skipDeprecated, - jdkVersion, - true, - linkMappings, - impliedPlatforms, - perPackageOptions, - externalDocumentationLinks, - noStdlibLink, + moduleName, + fullClasspath.map { it.absolutePath }, + sourceRoots, + samples.filterNotNull().map { project.file(it).absolutePath }, + includes.filterNotNull().map { project.file(it).absolutePath }, + outputDirectory, + outputFormat, + includeNonPublic, + false, + reportUndocumented, + skipEmptyPackages, + skipDeprecated, + jdkVersion, + true, + linkMappings, + impliedPlatforms, + perPackageOptions, + externalDocumentationLinks, + noStdlibLink, + noJdkLink, cacheRoot, collectSuppressedFiles(sourceRoots), languageVersion, - apiVersion) + apiVersion, + collectInheritedExtensionsFromLibraries + ) bootstrapProxy.configure( @@ -406,7 +448,9 @@ open class LinkMapping : Serializable, DokkaConfiguration.SourceLinkDefinition { var dir: String get() = path set(value) { - path = value + if (value.contains("\\")) + throw java.lang.IllegalArgumentException("Incorrect dir property, only Unix based path allowed.") + else path = value } override var path: String = "" diff --git a/runners/maven-plugin/build.gradle b/runners/maven-plugin/build.gradle index 79a8c22b..2e9d0b1b 100644 --- a/runners/maven-plugin/build.gradle +++ b/runners/maven-plugin/build.gradle @@ -19,7 +19,13 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } } +configurations { + maven +} + dependencies { + maven group: "org.apache.maven", name: 'apache-maven', version: maven_version, classifier: 'bin', ext: 'zip' + shadow project(":runners:fatjar") shadow "org.apache.maven:maven-core:$maven_version" shadow "org.apache.maven:maven-model:$maven_version" @@ -31,50 +37,96 @@ dependencies { shadow "com.github.olivergondza:maven-jdk-tools-wrapper:0.1" } +final File mavenHome = new File(buildDir, "maven-bin") +final File mvn = new File(mavenHome, "apache-maven-$maven_version/bin/mvn") + +tasks.clean.doLast { + delete mavenHome +} + +task setupMaven(type: Sync) { + from configurations.maven.collect{ zipTree(it) } + into "$buildDir/maven-bin" +} + +def mavenBuildDir = "$buildDir/maven" + + +sourceSets.main.resources { + srcDirs += "$mavenBuildDir/classes/java/main" + exclude "**/*.class" +} + task generatePom() { inputs.file(new File(projectDir, "pom.tpl.xml")) - outputs.file(new File(buildDir, "pom.xml")) + outputs.file(new File(mavenBuildDir, "pom.xml")) doLast { final pomTemplate = new File(projectDir, "pom.tpl.xml") - final pom = new File(buildDir, "pom.xml") + final pom = new File(mavenBuildDir, "pom.xml") + pom.parentFile.mkdirs() pom.text = pomTemplate.text.replace("<version>dokka_version</version>", "<version>$dokka_version</version>") .replace("<maven.version></maven.version>", "<maven.version>$maven_version</maven.version>") .replace("<version>maven-plugin-plugin</version>", "<version>$maven_plugin_tools_version</version>") } } - -task mergeClassOutputs doLast { - def sourceDir = new File(buildDir, "classes/kotlin") - def targetDir = new File(buildDir, "classes/java") - - sourceDir.eachFileRecurse FileType.ANY, { - def filePath = it.toPath() - def targetFilePath = targetDir.toPath().resolve(sourceDir.toPath().relativize(filePath)) - if (it.isFile()) { - Files.move(filePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING) - } else if (it.isDirectory()) { - targetFilePath.toFile().mkdirs() - } +// +//task mergeClassOutputs doLast { +// def sourceDir = new File(buildDir, "classes/kotlin") +// def targetDir = new File(buildDir, "classes/java") +// +// sourceDir.eachFileRecurse FileType.ANY, { +// def filePath = it.toPath() +// def targetFilePath = targetDir.toPath().resolve(sourceDir.toPath().relativize(filePath)) +// if (it.isFile()) { +// Files.move(filePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING) +// } else if (it.isDirectory()) { +// targetFilePath.toFile().mkdirs() +// } +// } +//} + + + +task syncKotlinClasses(type: Sync, dependsOn: compileKotlin) { + from "$buildDir/classes/kotlin" + into "$mavenBuildDir/classes/java" + + preserve { + include '**/*.class' } } -task pluginDescriptor(type: CrossPlatformExec) { - workingDir buildDir - commandLine mvn, '-e', '-B', 'org.apache.maven.plugins:maven-plugin-plugin:descriptor' +task syncJavaClasses(type: Sync, dependsOn: compileJava) { + from "$buildDir/classes/java" + into "$mavenBuildDir/classes/java" - dependsOn mergeClassOutputs + preserve { + include '**/*.class' + } } -task helpMojo(type: CrossPlatformExec) { - workingDir buildDir +task helpMojo(type: CrossPlatformExec, dependsOn: setupMaven) { + workingDir mavenBuildDir commandLine mvn, '-e', '-B', 'org.apache.maven.plugins:maven-plugin-plugin:helpmojo' - dependsOn mergeClassOutputs + dependsOn syncKotlinClasses } + +task pluginDescriptor(type: CrossPlatformExec, dependsOn: setupMaven) { + workingDir mavenBuildDir + commandLine mvn, '-e', '-B', 'org.apache.maven.plugins:maven-plugin-plugin:descriptor' + + dependsOn syncJavaClasses +} + + +//mergeClassOutputs.dependsOn compileKotlin +//helpMojo.dependsOn mergeClassOutputs helpMojo.dependsOn generatePom -sourceSets.main.java.srcDir("$buildDir/generated-sources/plugin") +sourceSets.main.java.srcDir("$buildDir/maven/generated-sources/plugin") compileJava.dependsOn helpMojo +processResources.dependsOn pluginDescriptor pluginDescriptor.dependsOn generatePom diff --git a/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt b/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt index 09da90c6..324703a0 100644 --- a/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt +++ b/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt @@ -4,6 +4,7 @@ import org.apache.maven.archiver.MavenArchiveConfiguration import org.apache.maven.archiver.MavenArchiver import org.apache.maven.execution.MavenSession import org.apache.maven.plugin.AbstractMojo +import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.plugins.annotations.* import org.apache.maven.project.MavenProject import org.apache.maven.project.MavenProjectHelper @@ -104,6 +105,9 @@ abstract class AbstractDokkaMojo : AbstractMojo() { @Parameter(defaultValue = "false") var noStdlibLink: Boolean = false + @Parameter(defaultValue = "false") + var noJdkLink: Boolean = false + @Parameter var cacheRoot: String? = null @@ -122,6 +126,12 @@ abstract class AbstractDokkaMojo : AbstractMojo() { return } + sourceLinks.forEach { + if (it.dir.contains("\\")) { + throw MojoExecutionException("Incorrect dir property, only Unix based path allowed.") + } + } + val gen = DokkaGenerator( MavenDokkaLogger(log), classpath, @@ -139,6 +149,7 @@ abstract class AbstractDokkaMojo : AbstractMojo() { perPackageOptions = perPackageOptions, externalDocumentationLinks = externalDocumentationLinks.map { it.build() }, noStdlibLink = noStdlibLink, + noJdkLink = noJdkLink, cacheRoot = cacheRoot, languageVersion = languageVersion, apiVersion = apiVersion @@ -196,7 +207,7 @@ class DokkaJavadocJarMojo : AbstractDokkaMojo() { /** * The archive configuration to use. - * See [Maven Archiver Reference](http://maven.apache.org/shared/maven-archiver/index.html) + * See [Maven Archiver Reference](https://maven.apache.org/shared/maven-archiver/index.html) */ @Parameter private val archive = MavenArchiveConfiguration() diff --git a/settings.gradle b/settings.gradle index 4dcfd255..ef5f9869 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ +rootProject.name = "dokka" + include 'core', 'integration', 'runners:fatjar', |