diff options
Diffstat (limited to 'core/src/main')
42 files changed, 1877 insertions, 1014 deletions
diff --git a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt index 8ad7b8bb..c816106e 100644 --- a/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt +++ b/core/src/main/kotlin/Analysis/AnalysisEnvironment.kt @@ -17,8 +17,15 @@ import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.io.URLUtil +import org.jetbrains.dokka.Analysis.DokkaJsAnalyzerFacade +import org.jetbrains.dokka.Analysis.DokkaNativeAnalyzerFacade import org.jetbrains.kotlin.analyzer.* +import org.jetbrains.kotlin.analyzer.common.CommonAnalysisParameters +import org.jetbrains.kotlin.analyzer.common.CommonAnalyzerFacade +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns +import org.jetbrains.kotlin.caches.project.LibraryModuleInfo import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.config.ContentRoot @@ -38,16 +45,17 @@ 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.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.resolve.JsPlatform import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.BindingTrace -import org.jetbrains.kotlin.resolve.CompilerEnvironment +import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.jvm.JvmAnalyzerFacade import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.resolve.konan.platform.KonanPlatform import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.types.KotlinType @@ -63,7 +71,7 @@ import java.io.File * $messageCollector: required by compiler infrastructure and will receive all compiler messages * $body: optional and can be used to configure environment without creating local variable */ -class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { +class AnalysisEnvironment(val messageCollector: MessageCollector, val analysisPlatform: Platform) : Disposable { val configuration = CompilerConfiguration() init { @@ -72,12 +80,19 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { fun createCoreEnvironment(): KotlinCoreEnvironment { System.setProperty("idea.io.use.fallback", "true") - val environment = KotlinCoreEnvironment.createForProduction(this, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) + + val configFiles = when (analysisPlatform) { + Platform.jvm, Platform.common -> EnvironmentConfigFiles.JVM_CONFIG_FILES + Platform.native -> EnvironmentConfigFiles.NATIVE_CONFIG_FILES + Platform.js -> EnvironmentConfigFiles.JS_CONFIG_FILES + } + val environment = KotlinCoreEnvironment.createForProduction(this, configuration, configFiles) val projectComponentManager = environment.project as MockComponentManager val projectFileIndex = CoreProjectFileIndex(environment.project, environment.configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS)) + val moduleManager = object : CoreModuleManager(environment.project, this) { override fun getModules(): Array<out Module> = arrayOf(projectFileIndex.module) } @@ -96,10 +111,11 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { return environment } - fun createSourceModuleSearchScope(project: Project, sourceFiles: List<KtFile>): GlobalSearchScope { - // TODO: Fix when going to implement dokka for JS - return TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles) - } + fun createSourceModuleSearchScope(project: Project, sourceFiles: List<KtFile>): GlobalSearchScope = + when (analysisPlatform) { + Platform.jvm -> TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles) + Platform.js, Platform.common, Platform.native -> GlobalSearchScope.filesScope(project, sourceFiles.map { it.virtualFile }.toSet()) + } fun createResolutionFacade(environment: KotlinCoreEnvironment): Pair<DokkaResolutionFacade, DokkaResolutionFacade> { @@ -108,7 +124,21 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { val sourceFiles = environment.getSourceFiles() - val library = object : ModuleInfo { + val targetPlatform = when (analysisPlatform) { + Platform.js -> JsPlatform + Platform.common -> TargetPlatform.Common + Platform.native -> KonanPlatform + Platform.jvm -> JvmPlatform + } + + val library = object : LibraryModuleInfo { + override val platform: TargetPlatform + get() = targetPlatform + + override fun getLibraryRoots(): Collection<String> { + return classpath.map { it.absolutePath } + } + override val name: Name = Name.special("<library>") override fun dependencies(): List<ModuleInfo> = listOf(this) } @@ -118,43 +148,146 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { } val sourcesScope = createSourceModuleSearchScope(environment.project, sourceFiles) + val modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo> = { + when (it) { + library -> ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope)) + module -> ModuleContent(it, emptyList(), GlobalSearchScope.allScope(environment.project)) + else -> throw IllegalArgumentException("Unexpected module info") + } + } - val builtIns = JvmBuiltIns(projectContext.storageManager) + var builtIns: JvmBuiltIns? = null + val resolverForProject = when (analysisPlatform) { + Platform.jvm -> { + builtIns = JvmBuiltIns(projectContext.storageManager) + createJvmResolverForProject(projectContext, module, library, modulesContent, sourcesScope, builtIns) + } + Platform.common -> createCommonResolverForProject(projectContext, module, library, modulesContent, environment) + Platform.js -> createJsResolverForProject(projectContext, module, library, modulesContent) + Platform.native -> createNativeResolverForProject(projectContext, module, library, modulesContent) - val javaRoots = classpath - .mapNotNull { - val rootFile = when { - it.extension == "jar" -> - StandardFileSystems.jar().findFileByPath("${it.absolutePath}${URLUtil.JAR_SEPARATOR}") - else -> - StandardFileSystems.local().findFileByPath(it.absolutePath) - } + } + val resolverForLibrary = resolverForProject.resolverForModule(library) // Required before module to initialize library properly + val resolverForModule = resolverForProject.resolverForModule(module) + val libraryModuleDescriptor = resolverForProject.descriptorForModule(library) + val moduleDescriptor = resolverForProject.descriptorForModule(module) + builtIns?.initialize(moduleDescriptor, true) + val libraryResolutionFacade = DokkaResolutionFacade(environment.project, libraryModuleDescriptor, resolverForLibrary) + val created = DokkaResolutionFacade(environment.project, moduleDescriptor, resolverForModule) + val projectComponentManager = environment.project as MockComponentManager + projectComponentManager.registerService(KotlinCacheService::class.java, CoreKotlinCacheService(created)) - rootFile?.let { JavaRoot(it, JavaRoot.RootType.BINARY) } + return created to libraryResolutionFacade + } + + private fun createCommonResolverForProject( + projectContext: ProjectContext, + module: ModuleInfo, + library: LibraryModuleInfo, + modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>, + environment: KotlinCoreEnvironment + ): ResolverForProjectImpl<ModuleInfo> { + return ResolverForProjectImpl( + debugName = "Dokka", + projectContext = projectContext, + modules = listOf(module, library), + modulesContent = modulesContent, + modulePlatforms = { MultiTargetPlatform.Common }, + moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */, + resolverForModuleFactoryByPlatform = { CommonAnalyzerFacade }, + platformParameters = { _ -> + CommonAnalysisParameters { content -> + environment.createPackagePartProvider(content.moduleContentScope) + } + }, + targetEnvironment = CompilerEnvironment, + builtIns = DefaultBuiltIns.Instance + ) + } + + private fun createJsResolverForProject( + projectContext: ProjectContext, + module: ModuleInfo, + library: LibraryModuleInfo, + modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo> + ): ResolverForProjectImpl<ModuleInfo> { + return ResolverForProjectImpl( + debugName = "Dokka", + projectContext = projectContext, + modules = listOf(module, library), + modulesContent = modulesContent, + modulePlatforms = { JsPlatform.multiTargetPlatform }, + moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */, + resolverForModuleFactoryByPlatform = { DokkaJsAnalyzerFacade }, + platformParameters = { _ -> PlatformAnalysisParameters.Empty }, + targetEnvironment = CompilerEnvironment, + builtIns = JsPlatform.builtIns + ) + } + + private fun createNativeResolverForProject( + projectContext: ProjectContext, + module: ModuleInfo, + library: LibraryModuleInfo, + modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo> + ): ResolverForProjectImpl<ModuleInfo> { + return ResolverForProjectImpl( + debugName = "Dokka", + projectContext = projectContext, + modules = listOf(module, library), + modulesContent = modulesContent, + modulePlatforms = { KonanPlatform.multiTargetPlatform }, + moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */, + resolverForModuleFactoryByPlatform = { DokkaNativeAnalyzerFacade }, + platformParameters = { _ -> PlatformAnalysisParameters.Empty }, + targetEnvironment = CompilerEnvironment + ) + + } + + private fun createJvmResolverForProject( + projectContext: ProjectContext, + module: ModuleInfo, + library: LibraryModuleInfo, + modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>, + sourcesScope: GlobalSearchScope, + builtIns: KotlinBuiltIns + ): ResolverForProjectImpl<ModuleInfo> { + val javaRoots = classpath + .mapNotNull { + val rootFile = when { + it.extension == "jar" -> + StandardFileSystems.jar().findFileByPath("${it.absolutePath}${URLUtil.JAR_SEPARATOR}") + else -> + StandardFileSystems.local().findFileByPath(it.absolutePath) } - val resolverForProject = ResolverForProjectImpl( - "Dokka", - projectContext, - listOf(library, module), - { + rootFile?.let { JavaRoot(it, JavaRoot.RootType.BINARY) } + } + + return ResolverForProjectImpl( + debugName = "Dokka", + projectContext = projectContext, + modules = listOf(library, module), + modulesContent = { 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 }, - { + modulePlatforms = { JvmPlatform.multiTargetPlatform }, + moduleLanguageSettingsProvider = LanguageSettingsProvider.Default /* TODO: Fix this */, + resolverForModuleFactoryByPlatform = { JvmAnalyzerFacade }, + platformParameters = { JvmPlatformParameters ({ content -> - JvmPackagePartProvider(configuration.languageVersionSettings, content.moduleContentScope).apply { - addRoots(javaRoots, messageCollector) - } + JvmPackagePartProvider( + configuration.languageVersionSettings, + content.moduleContentScope) + .apply { + addRoots(javaRoots, messageCollector) + } }, { val file = (it as JavaClassImpl).psi.containingFile.virtualFile if (file in sourcesScope) @@ -163,21 +296,9 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { library }) }, - CompilerEnvironment, + targetEnvironment = CompilerEnvironment, builtIns = builtIns ) - - 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 to libraryResolutionFacade } fun loadLanguageVersionSettings(languageVersionString: String?, apiVersionString: String?) { @@ -197,6 +318,9 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { * $paths: collection of files to add */ fun addClasspath(paths: List<File>) { + if (analysisPlatform == Platform.js) { + configuration.addAll(JSConfigurationKeys.LIBRARIES, paths.map { it.absolutePath }) + } configuration.addJvmClasspathRoots(paths) } @@ -205,6 +329,9 @@ class AnalysisEnvironment(val messageCollector: MessageCollector) : Disposable { * $path: path to add */ fun addClasspath(path: File) { + if (analysisPlatform == Platform.js) { + configuration.add(JSConfigurationKeys.LIBRARIES, path.absolutePath) + } configuration.addJvmClasspathRoot(path) } diff --git a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt index 319d85b1..f5fbf991 100644 --- a/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt +++ b/core/src/main/kotlin/Analysis/CoreProjectFileIndex.kt @@ -166,8 +166,7 @@ class CoreProjectFileIndex(private val project: Project, contentRoots: List<Cont private val sdk: Sdk = object : Sdk, RootProvider { override fun getFiles(rootType: OrderRootType): Array<out VirtualFile> = classpathRoots - .map { StandardFileSystems.local().findFileByPath(it.file.path) } - .filterNotNull() + .mapNotNull { StandardFileSystems.local().findFileByPath(it.file.path) } .toTypedArray() override fun addRootSetChangedListener(p0: RootProvider.RootSetChangedListener) { diff --git a/core/src/main/kotlin/Analysis/DokkaAnalyzerFacades.kt b/core/src/main/kotlin/Analysis/DokkaAnalyzerFacades.kt new file mode 100644 index 00000000..874341dd --- /dev/null +++ b/core/src/main/kotlin/Analysis/DokkaAnalyzerFacades.kt @@ -0,0 +1,164 @@ +package org.jetbrains.dokka.Analysis + +import org.jetbrains.kotlin.analyzer.* +import org.jetbrains.kotlin.caches.project.LibraryModuleInfo +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.config.TargetPlatformVersion +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.get +import org.jetbrains.kotlin.container.useImpl +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.context.ModuleContext +import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.frontend.di.configureModule +import org.jetbrains.kotlin.ide.konan.KOTLIN_NATIVE_CURRENT_ABI_VERSION +import org.jetbrains.kotlin.ide.konan.createPackageFragmentProvider +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.js.resolve.JsPlatform +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.library.createKonanLibrary +import org.jetbrains.kotlin.resolve.* +import org.jetbrains.kotlin.resolve.konan.platform.KonanPlatform +import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService +import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil +import org.jetbrains.kotlin.serialization.js.createKotlinJavascriptPackageFragmentProvider +import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils + +fun createContainerForLazyResolve( + moduleContext: ModuleContext, + declarationProviderFactory: DeclarationProviderFactory, + bindingTrace: BindingTrace, + platform: TargetPlatform, + targetPlatformVersion: TargetPlatformVersion, + targetEnvironment: TargetEnvironment, + languageVersionSettings: LanguageVersionSettings +): StorageComponentContainer = createContainer("LazyResolve", platform) { + configureModule(moduleContext, platform, targetPlatformVersion, bindingTrace) + + useInstance(declarationProviderFactory) + useInstance(languageVersionSettings) + + useImpl<AnnotationResolverImpl>() + useImpl<CompilerDeserializationConfiguration>() + targetEnvironment.configure(this) + + useImpl<ResolveSession>() + useImpl<LazyTopDownAnalyzer>() +} + + +object DokkaJsAnalyzerFacade : ResolverForModuleFactory() { + override fun <M : ModuleInfo> createResolverForModule( + moduleDescriptor: ModuleDescriptorImpl, + moduleContext: ModuleContext, + moduleContent: ModuleContent<M>, + platformParameters: PlatformAnalysisParameters, + targetEnvironment: TargetEnvironment, + resolverForProject: ResolverForProject<M>, + languageVersionSettings: LanguageVersionSettings, + targetPlatformVersion: TargetPlatformVersion + ): ResolverForModule { + val (moduleInfo, syntheticFiles, moduleContentScope) = moduleContent + val project = moduleContext.project + val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( + project, + moduleContext.storageManager, + syntheticFiles, + moduleContentScope, + moduleInfo + ) + + val container = createContainerForLazyResolve( + moduleContext, + declarationProviderFactory, + BindingTraceContext(), + JsPlatform, + TargetPlatformVersion.NoVersion, + targetEnvironment, + languageVersionSettings + ) + var packageFragmentProvider = container.get<ResolveSession>().packageFragmentProvider + + if (moduleInfo is LibraryModuleInfo && moduleInfo.platform == JsPlatform) { + val providers = moduleInfo.getLibraryRoots() + .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) } + .filter { it.version.isCompatible() } + .map { metadata -> + val (header, packageFragmentProtos) = + KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version) + createKotlinJavascriptPackageFragmentProvider( + moduleContext.storageManager, moduleDescriptor, header, packageFragmentProtos, metadata.version, + container.get(), LookupTracker.DO_NOTHING + ) + } + + if (providers.isNotEmpty()) { + packageFragmentProvider = CompositePackageFragmentProvider(listOf(packageFragmentProvider) + providers) + } + } + + return ResolverForModule(packageFragmentProvider, container) + } + + override val targetPlatform: TargetPlatform + get() = JsPlatform +} + +object DokkaNativeAnalyzerFacade : ResolverForModuleFactory() { + override val targetPlatform: TargetPlatform + get() = KonanPlatform + + override fun <M : ModuleInfo> createResolverForModule( + moduleDescriptor: ModuleDescriptorImpl, + moduleContext: ModuleContext, + moduleContent: ModuleContent<M>, + platformParameters: PlatformAnalysisParameters, + targetEnvironment: TargetEnvironment, + resolverForProject: ResolverForProject<M>, + languageVersionSettings: LanguageVersionSettings, + targetPlatformVersion: TargetPlatformVersion + ): ResolverForModule { + + val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( + moduleContext.project, + moduleContext.storageManager, + moduleContent.syntheticFiles, + moduleContent.moduleContentScope, + moduleContent.moduleInfo + ) + + val container = createContainerForLazyResolve( + moduleContext, + declarationProviderFactory, + BindingTraceContext(), + targetPlatform, + TargetPlatformVersion.NoVersion, + targetEnvironment, + languageVersionSettings + ) + + val packageFragmentProvider = container.get<ResolveSession>().packageFragmentProvider + val fragmentProviders = mutableListOf(packageFragmentProvider) + + val moduleInfo = moduleContent.moduleInfo + + if (moduleInfo is LibraryModuleInfo) { + moduleInfo.getLibraryRoots() + .filter { File(it).extension != "jar" } + .map { createKonanLibrary(File(it), KOTLIN_NATIVE_CURRENT_ABI_VERSION) } + .mapTo(fragmentProviders) { + it.createPackageFragmentProvider( + moduleContext.storageManager, + languageVersionSettings, + moduleDescriptor + ) + } + + } + + return ResolverForModule(CompositePackageFragmentProvider(fragmentProviders), container) + } +} diff --git a/core/src/main/kotlin/DokkaBootstrapImpl.kt b/core/src/main/kotlin/DokkaBootstrapImpl.kt index e18ab6cf..b48b62d4 100644 --- a/core/src/main/kotlin/DokkaBootstrapImpl.kt +++ b/core/src/main/kotlin/DokkaBootstrapImpl.kt @@ -1,8 +1,8 @@ package org.jetbrains.dokka +import com.google.gson.Gson import org.jetbrains.dokka.DokkaConfiguration.PackageOptions -import ru.yole.jkid.deserialization.deserialize -import java.io.File + import java.util.function.BiConsumer @@ -39,42 +39,38 @@ class DokkaBootstrapImpl : DokkaBootstrap { } lateinit var generator: DokkaGenerator + val gson = Gson() - override fun configure(logger: BiConsumer<String, String>, serializedConfigurationJSON: String) - = configure(DokkaProxyLogger(logger), deserialize<DokkaConfigurationImpl>(serializedConfigurationJSON)) - - fun configure(logger: DokkaLogger, configuration: DokkaConfiguration) = with(configuration) { - generator = DokkaGenerator( - logger, - classpath, - sourceRoots, - samples, - includes, - moduleName, - DocumentationOptions( - 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 - ) - ) + fun configure(logger: DokkaLogger, configuration: DokkaConfigurationImpl) = with(configuration) { + + fun defaultLinks(config: PassConfigurationImpl): List<ExternalDocumentationLinkImpl> { + val links = mutableListOf<ExternalDocumentationLinkImpl>() + if (!config.noJdkLink) + links += DokkaConfiguration.ExternalDocumentationLink + .Builder("https://docs.oracle.com/javase/${config.jdkVersion}/docs/api/") + .build() as ExternalDocumentationLinkImpl + + if (!config.noStdlibLink) + links += DokkaConfiguration.ExternalDocumentationLink + .Builder("https://kotlinlang.org/api/latest/jvm/stdlib/") + .build() as ExternalDocumentationLinkImpl + return links + } + + val configurationWithLinks = + configuration.copy(passesConfigurations = + passesConfigurations + .map { + val links: List<ExternalDocumentationLinkImpl> = it.externalDocumentationLinks + defaultLinks(it) + it.copy(externalDocumentationLinks = links) + } + ) + + generator = DokkaGenerator(configurationWithLinks, logger) } + override fun configure(logger: BiConsumer<String, String>, serializedConfigurationJSON: String) + = configure(DokkaProxyLogger(logger), gson.fromJson(serializedConfigurationJSON, DokkaConfigurationImpl::class.java)) + override fun generate() = generator.generate() -}
\ No newline at end of file +} diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt index b497fb0f..4bac8aa0 100644 --- a/core/src/main/kotlin/Formats/FormatDescriptor.kt +++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt @@ -25,6 +25,7 @@ abstract class FileGeneratorBasedFormatDescriptor : FormatDescriptor { override fun configureOutput(binder: Binder): Unit = with(binder) { bind<Generator>() toType NodeLocationAwareGenerator::class bind<NodeLocationAwareGenerator>() toType generatorServiceClass + bind(generatorServiceClass.java) // https://github.com/google/guice/issues/847 bind<LanguageService>() toType languageServiceClass diff --git a/core/src/main/kotlin/Formats/FormatService.kt b/core/src/main/kotlin/Formats/FormatService.kt index 63f25008..8f4855e3 100644 --- a/core/src/main/kotlin/Formats/FormatService.kt +++ b/core/src/main/kotlin/Formats/FormatService.kt @@ -22,7 +22,7 @@ interface FormatService { } interface FormattedOutputBuilder { - /** Appends formatted content to [StringBuilder](to) using specified [location] */ + /** Appends formatted content */ fun appendNodes(nodes: Iterable<DocumentationNode>) } diff --git a/core/src/main/kotlin/Formats/HtmlFormatService.kt b/core/src/main/kotlin/Formats/HtmlFormatService.kt index 0ad946be..d36ea0a2 100644 --- a/core/src/main/kotlin/Formats/HtmlFormatService.kt +++ b/core/src/main/kotlin/Formats/HtmlFormatService.kt @@ -88,9 +88,7 @@ open class HtmlOutputBuilder(to: StringBuilder, to.append(" ") } - override fun ensureParagraph() { - - } + override fun ensureParagraph() {} } open class HtmlFormatService @Inject constructor(generator: NodeLocationAwareGenerator, @@ -144,7 +142,7 @@ fun formatPageTitle(node: DocumentationNode): String { } val qName = qualifiedNameForPageTitle(node) - return qName + " - " + moduleName + return "$qName - $moduleName" } private fun qualifiedNameForPageTitle(node: DocumentationNode): String { diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt deleted file mode 100644 index a98002d4..00000000 --- a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt +++ /dev/null @@ -1,224 +0,0 @@ -package org.jetbrains.dokka - -import com.google.inject.Inject -import com.google.inject.name.Named -import org.jetbrains.dokka.Utilities.impliedPlatformsName -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty - - -open class KotlinWebsiteOutputBuilder( - to: StringBuilder, - location: Location, - generator: NodeLocationAwareGenerator, - languageService: LanguageService, - extension: String, - impliedPlatforms: List<String> -) : JekyllOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) { - private var needHardLineBreaks = false - private var insideDiv = 0 - - override fun appendFrontMatter(nodes: Iterable<DocumentationNode>, to: StringBuilder) { - super.appendFrontMatter(nodes, to) - to.appendln("layout: api") - } - - override fun appendBreadcrumbs(path: Iterable<FormatLink>) { - if (path.count() > 1) { - to.append("<div class='api-docs-breadcrumbs'>") - super.appendBreadcrumbs(path) - to.append("</div>") - } - } - - override fun appendCode(body: () -> Unit) = wrapIfNotEmpty("<code>", "</code>", body) - - override fun appendStrikethrough(body: () -> Unit) = wrapInTag("s", body) - - protected fun div(to: StringBuilder, cssClass: String, otherAttributes: String = "", markdown: Boolean = false, block: () -> Unit) { - to.append("<div class=\"$cssClass\"$otherAttributes") - if (markdown) to.append(" markdown=\"1\"") - to.append(">") - if (!markdown) insideDiv++ - block() - if (!markdown) insideDiv-- - to.append("</div>\n") - } - - override fun appendAsSignature(node: ContentNode, block: () -> Unit) { - val contentLength = node.textLength - if (contentLength == 0) return - div(to, "signature") { - needHardLineBreaks = contentLength >= 62 - try { - block() - } finally { - needHardLineBreaks = false - } - } - } - - override fun appendAsOverloadGroup(to: StringBuilder, platforms: Set<String>, block: () -> Unit) { - div(to, "overload-group", calculateDataAttributes(platforms), true) { - ensureParagraph() - block() - ensureParagraph() - } - } - - override fun appendLink(href: String, body: () -> Unit) = wrap("<a href=\"$href\">", "</a>", body) - - override fun appendHeader(level: Int, body: () -> Unit) { - if (insideDiv > 0) { - wrapInTag("p", body, newlineAfterClose = true) - } else { - super.appendHeader(level, body) - } - } - - override fun appendLine() { - if (insideDiv > 0) { - to.appendln("<br/>") - } else { - super.appendLine() - } - } - - override fun appendTable(vararg columns: String, body: () -> Unit) { - to.appendln("<table class=\"api-docs-table\">") - body() - to.appendln("</table>") - } - - override fun appendTableBody(body: () -> Unit) { - to.appendln("<tbody>") - body() - to.appendln("</tbody>") - } - - override fun appendTableRow(body: () -> Unit) { - to.appendln("<tr>") - body() - to.appendln("</tr>") - } - - override fun appendTableCell(body: () -> Unit) { - to.appendln("<td markdown=\"1\">") - body() - to.appendln("\n</td>") - } - - override fun appendBlockCode(language: String, body: () -> Unit) { - if (language.isNotEmpty()) { - super.appendBlockCode(language, body) - } else { - wrap("<pre markdown=\"1\">", "</pre>", body) - } - } - - override fun appendSymbol(text: String) { - to.append("<span class=\"symbol\">${text.htmlEscape()}</span>") - } - - override fun appendKeyword(text: String) { - to.append("<span class=\"keyword\">${text.htmlEscape()}</span>") - } - - override fun appendIdentifier(text: String, kind: IdentifierKind, signature: String?) { - val id = signature?.let { " id=\"$it\"" }.orEmpty() - to.append("<span class=\"${identifierClassName(kind)}\"$id>${text.htmlEscape()}</span>") - } - - override fun appendSoftLineBreak() { - if (needHardLineBreaks) - to.append("<br/>") - - } - - override fun appendIndentedSoftLineBreak() { - if (needHardLineBreaks) { - to.append("<br/> ") - } - } - - private fun identifierClassName(kind: IdentifierKind) = when (kind) { - IdentifierKind.ParameterName -> "parameterName" - IdentifierKind.SummarizedTypeName -> "summarizedTypeName" - else -> "identifier" - } - - fun calculateDataAttributes(platforms: Set<String>): String { - fun String.isKotlinVersion() = this.startsWith("Kotlin") - fun String.isJREVersion() = this.startsWith("JRE") - val kotlinVersion = platforms.singleOrNull(String::isKotlinVersion) - val jreVersion = platforms.singleOrNull(String::isJREVersion) - val targetPlatforms = platforms.filterNot { it.isKotlinVersion() || it.isJREVersion() } - - val kotlinVersionAttr = kotlinVersion?.let { " data-kotlin-version=\"$it\"" } ?: "" - val jreVersionAttr = jreVersion?.let { " data-jre-version=\"$it\"" } ?: "" - val platformsAttr = targetPlatforms.ifNotEmpty { " data-platform=\"${targetPlatforms.joinToString()}\"" } ?: "" - return "$platformsAttr$kotlinVersionAttr$jreVersionAttr" - } - - override fun appendIndexRow(platforms: Set<String>, block: () -> Unit) { - if (platforms.isNotEmpty()) - wrap("<tr${calculateDataAttributes(platforms)}>", "</tr>", block) - else - appendTableRow(block) - } - - override fun appendPlatforms(platforms: Set<String>) { - - } -} - -class KotlinWebsiteFormatService @Inject constructor( - generator: NodeLocationAwareGenerator, - signatureGenerator: LanguageService, - @Named(impliedPlatformsName) impliedPlatforms: List<String>, - logger: DokkaLogger -) : JekyllFormatService(generator, signatureGenerator, "html", impliedPlatforms) { - init { - logger.warn("Format kotlin-website deprecated and will be removed in next release") - } - - override fun createOutputBuilder(to: StringBuilder, location: Location) = - KotlinWebsiteOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) -} - - -class KotlinWebsiteRunnableSamplesOutputBuilder( - to: StringBuilder, - location: Location, - generator: NodeLocationAwareGenerator, - languageService: LanguageService, - extension: String, - impliedPlatforms: List<String> -) : KotlinWebsiteOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) { - - override fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) { - div(to, "sample", markdown = true) { - appendBlockCode(language) { - imports() - wrap("\n\nfun main(args: Array<String>) {", "}") { - wrap("\n//sampleStart\n", "\n//sampleEnd\n", body) - } - } - } - } -} - -class KotlinWebsiteRunnableSamplesFormatService @Inject constructor( - generator: NodeLocationAwareGenerator, - signatureGenerator: LanguageService, - @Named(impliedPlatformsName) impliedPlatforms: List<String>, - logger: DokkaLogger -) : JekyllFormatService(generator, signatureGenerator, "html", impliedPlatforms) { - - init { - logger.warn("Format kotlin-website-samples deprecated and will be removed in next release") - } - - override fun createOutputBuilder(to: StringBuilder, location: Location) = - KotlinWebsiteRunnableSamplesOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms) -} - diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt index 6ced75b5..3cdea156 100644 --- a/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt +++ b/core/src/main/kotlin/Formats/KotlinWebsiteHtmlFormatService.kt @@ -3,7 +3,6 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.google.inject.name.Named import org.jetbrains.dokka.Utilities.impliedPlatformsName -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File @@ -20,7 +19,7 @@ open class KotlinWebsiteHtmlOutputBuilder( generator: NodeLocationAwareGenerator, languageService: LanguageService, extension: String, - impliedPlatforms: List<String>, + val impliedPlatforms: List<String>, templateService: HtmlTemplateService ) : HtmlOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms, templateService) { private var needHardLineBreaks = false @@ -36,6 +35,12 @@ open class KotlinWebsiteHtmlOutputBuilder( } } + override fun appendSinceKotlin(version: String) { + } + + override fun appendSinceKotlinWrapped(version: String) { + } + override fun appendCode(body: () -> Unit) = wrapIfNotEmpty("<code>", "</code>", body) protected fun div(to: StringBuilder, cssClass: String, otherAttributes: String = "", block: () -> Unit) { @@ -60,7 +65,7 @@ open class KotlinWebsiteHtmlOutputBuilder( } } - override fun appendAsOverloadGroup(to: StringBuilder, platforms: Set<String>, block: () -> Unit) { + override fun appendAsOverloadGroup(to: StringBuilder, platforms: PlatformsData, block: () -> Unit) { div(to, "overload-group", calculateDataAttributes(platforms)) { block() } @@ -69,27 +74,29 @@ open class KotlinWebsiteHtmlOutputBuilder( override fun appendLink(href: String, body: () -> Unit) = wrap("<a href=\"$href\">", "</a>", body) override fun appendTable(vararg columns: String, body: () -> Unit) { - to.appendln("<table class=\"api-docs-table\">") - body() - to.appendln("</table>") + //to.appendln("<table class=\"api-docs-table\">") + div(to, "api-declarations-list") { + body() + } + //to.appendln("</table>") } override fun appendTableBody(body: () -> Unit) { - to.appendln("<tbody>") + //to.appendln("<tbody>") body() - to.appendln("</tbody>") + //to.appendln("</tbody>") } override fun appendTableRow(body: () -> Unit) { - to.appendln("<tr>") + //to.appendln("<tr>") body() - to.appendln("</tr>") + //to.appendln("</tr>") } override fun appendTableCell(body: () -> Unit) { - to.appendln("<td>") +// to.appendln("<td>") body() - to.appendln("\n</td>") +// to.appendln("\n</td>") } override fun appendSymbol(text: String) { @@ -122,34 +129,79 @@ open class KotlinWebsiteHtmlOutputBuilder( else -> "identifier" } - fun calculateDataAttributes(platforms: Set<String>): String { - fun String.isKotlinVersion() = this.startsWith("Kotlin") - fun String.isJREVersion() = this.startsWith("JRE") - val kotlinVersion = platforms.singleOrNull(String::isKotlinVersion) - val jreVersion = platforms.singleOrNull(String::isJREVersion) - val targetPlatforms = platforms.filterNot { it.isKotlinVersion() || it.isJREVersion() } + private data class PlatformsForElement( + val platformToVersion: Map<String, String> + ) + + private fun calculatePlatforms(platforms: PlatformsData): PlatformsForElement { + //val kotlinVersion = platforms.singleOrNull(String::isKotlinVersion)?.removePrefix("Kotlin ") + val jreVersion = platforms.keys.filter(String::isJREVersion).min()?.takeUnless { it.endsWith("6") } + val targetPlatforms = platforms.filterNot { it.key.isJREVersion() } + + listOfNotNull(jreVersion?.let { it to platforms[it]!! }) + + return PlatformsForElement( + targetPlatforms.mapValues { (_, nodes) -> effectiveSinceKotlinForNodes(nodes) } + ) + } - val kotlinVersionAttr = kotlinVersion?.let { " data-kotlin-version=\"$it\"" } ?: "" - val jreVersionAttr = jreVersion?.let { " data-jre-version=\"$it\"" } ?: "" - val platformsAttr = targetPlatforms.ifNotEmpty { " data-platform=\"${targetPlatforms.joinToString()}\"" } ?: "" - return "$platformsAttr$kotlinVersionAttr$jreVersionAttr" + private fun calculateDataAttributes(platforms: PlatformsData): String { + val platformToVersion = calculatePlatforms(platforms).platformToVersion + val (platformNames, versions) = platformToVersion.toList().unzip() + return "data-platform=\"${platformNames.joinToString()}\" "+ + "data-kotlin-version=\"${versions.joinToString()}\"" } - override fun appendIndexRow(platforms: Set<String>, block: () -> Unit) { - if (platforms.isNotEmpty()) - wrap("<tr${calculateDataAttributes(platforms)}>", "</tr>", block) - else - appendTableRow(block) + override fun appendIndexRow(platforms: PlatformsData, block: () -> Unit) { +// if (platforms.isNotEmpty()) +// wrap("<tr${calculateDataAttributes(platforms)}>", "</tr>", block) +// else +// appendTableRow(block) + div(to, "declarations", otherAttributes = " ${calculateDataAttributes(platforms)}") { + block() + } + } + + override fun appendPlatforms(platforms: PlatformsData) { + val platformToVersion = calculatePlatforms(platforms).platformToVersion + div(to, "tags") { + div(to, "spacer") {} + platformToVersion.entries.sortedBy { + platformSortWeight(it.key) + }.forEach { (platform, version) -> + div(to, "tags__tag platform tag-value-$platform", + otherAttributes = " data-tag-version=\"$version\"") { + to.append(platform) + } + } + div(to, "tags__tag kotlin-version") { + to.append(mergeVersions(platformToVersion.values.toList())) + } + } } - override fun appendPlatforms(platforms: Set<String>) {} + override fun appendAsNodeDescription(platforms: PlatformsData, block: () -> Unit) { + div(to, "node-page-main", otherAttributes = " ${calculateDataAttributes(platforms)}") { + block() + } + + } override fun appendBreadcrumbSeparator() { to.append(" / ") } + override fun appendPlatformsAsText(platforms: PlatformsData) { + appendHeader(5) { + val filtered = platforms.keys.filterNot { it.isJREVersion() }.sortedBy { platformSortWeight(it) } + if (filtered.isNotEmpty()) { + to.append("For ") + filtered.joinTo(to) + } + } + } + override fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) { - div(to, "sample") { + div(to, "sample", otherAttributes = " data-min-compiler-version=\"1.3\"") { appendBlockCode(language) { imports() wrap("\n\nfun main(args: Array<String>) {".htmlEscape(), "}") { @@ -169,6 +221,29 @@ open class KotlinWebsiteHtmlOutputBuilder( appendContent(section) } } + + override fun appendAsPlatformDependentBlock(platforms: PlatformsData, block: (PlatformsData) -> Unit) { + if (platforms.isNotEmpty()) + wrap("<div ${calculateDataAttributes(platforms)}>", "</div>") { + block(platforms) + } + else + block(platforms) + } + + override fun appendAsSummaryGroup(platforms: PlatformsData, block: (PlatformsData) -> Unit) { + div(to, "summary-group", otherAttributes = " ${calculateDataAttributes(platforms)}") { + block(platforms) + } + } + + fun platformSortWeight(name: String) = when(name.toLowerCase()) { + "common" -> 0 + "jvm" -> 1 + "js" -> 3 + "native" -> 4 + else -> 2 // This is hack to support JRE/JUnit and so on + } } class KotlinWebsiteHtmlFormatService @Inject constructor( @@ -184,3 +259,6 @@ class KotlinWebsiteHtmlFormatService @Inject constructor( KotlinWebsiteHtmlOutputBuilder(to, location, generator, languageService, extension, impliedPlatforms, templateService) } + +private fun String.isKotlinVersion() = this.startsWith("Kotlin") +private fun String.isJREVersion() = this.startsWith("JRE", ignoreCase=true)
\ No newline at end of file diff --git a/core/src/main/kotlin/Formats/MarkdownFormatService.kt b/core/src/main/kotlin/Formats/MarkdownFormatService.kt index 4265394f..216dd2ef 100644 --- a/core/src/main/kotlin/Formats/MarkdownFormatService.kt +++ b/core/src/main/kotlin/Formats/MarkdownFormatService.kt @@ -159,24 +159,35 @@ open class MarkdownOutputBuilder(to: StringBuilder, } override fun appendParagraph(body: () -> Unit) { - if (inTableCell) { - ensureNewline() - body() - } else if (listStack.isNotEmpty()) { - body() - ensureNewline() - } else { - ensureParagraph() - body() - ensureParagraph() + when { + inTableCell -> { + ensureNewline() + body() + } + listStack.isNotEmpty() -> { + body() + ensureNewline() + } + else -> { + ensureParagraph() + body() + ensureParagraph() + } } } override fun appendHeader(level: Int, body: () -> Unit) { - ensureParagraph() - to.append("${"#".repeat(level)} ") - body() - ensureParagraph() + when { + inTableCell -> { + body() + } + else -> { + ensureParagraph() + to.append("${"#".repeat(level)} ") + body() + ensureParagraph() + } + } } override fun appendBlockCode(language: String, body: () -> Unit) { diff --git a/core/src/main/kotlin/Formats/PackageListService.kt b/core/src/main/kotlin/Formats/PackageListService.kt index 7b68098e..e675d927 100644 --- a/core/src/main/kotlin/Formats/PackageListService.kt +++ b/core/src/main/kotlin/Formats/PackageListService.kt @@ -32,10 +32,13 @@ class DefaultPackageListService @Inject constructor( node.members.forEach { visit(it, relocated = true) } } NodeKind.GroupNode -> { - //only children of top-level GN records interesting for us, since link to top-level ones should point to GN - node.members.forEach { it.members.forEach { visit(it, relocated = true) } } - //record signature of GN as signature of type alias and class merged to GN, so link to it should point to GN - node.detailOrNull(NodeKind.Signature)?.let { visit(it, relocated = true) } + if (node.members.isNotEmpty()) { + // Only nodes only has single file is need to be relocated + // TypeAliases for example + node.origins + .filter { it.members.isEmpty() } + .forEach { visit(it, relocated = true) } + } } else -> { if (nodeKind in NodeKind.classLike || nodeKind in NodeKind.memberLike) { diff --git a/core/src/main/kotlin/Formats/StandardFormats.kt b/core/src/main/kotlin/Formats/StandardFormats.kt index dd67ac97..86f70a37 100644 --- a/core/src/main/kotlin/Formats/StandardFormats.kt +++ b/core/src/main/kotlin/Formats/StandardFormats.kt @@ -31,17 +31,6 @@ class HtmlFormatDescriptor : HtmlFormatDescriptorBase(), DefaultAnalysisComponen class HtmlAsJavaFormatDescriptor : HtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsJava -class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() { - override val formatServiceClass = KotlinWebsiteFormatService::class - override val outlineServiceClass = YamlOutlineService::class -} - -class KotlinWebsiteFormatRunnableSamplesDescriptor : KotlinFormatDescriptorBase() { - override val formatServiceClass = KotlinWebsiteRunnableSamplesFormatService::class - override val sampleProcessingService = KotlinWebsiteSampleProcessingService::class - override val outlineServiceClass = YamlOutlineService::class -} - class KotlinWebsiteHtmlFormatDescriptor : KotlinFormatDescriptorBase() { override val formatServiceClass = KotlinWebsiteHtmlFormatService::class override val sampleProcessingService = KotlinWebsiteSampleProcessingService::class diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt index 410de281..7299670e 100644 --- a/core/src/main/kotlin/Formats/StructuredFormatService.kt +++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt @@ -1,18 +1,59 @@ package org.jetbrains.dokka import org.jetbrains.dokka.LanguageService.RenderMode +import org.jetbrains.kotlin.utils.keysToMap import java.util.* data class FormatLink(val text: String, val href: String) +private data class Summarized( + val data: List<SummarizedBySummary> +) { + + constructor(data: Map<ContentNode, Map<ContentNode, List<DocumentationNode>>>) : this( + data.entries.map { (summary, signatureToMember) -> + SummarizedBySummary( + summary, + signatureToMember.map { (signature, nodes) -> + SummarizedNodes(signature, nodes) + } + ) + } + ) + + data class SummarizedNodes(val content: ContentNode, val nodes: List<DocumentationNode>) { + val platforms = effectivePlatformsForMembers(nodes) + } + data class SummarizedBySummary(val content: ContentNode, val signatures: List<SummarizedNodes>) { + val platforms = effectivePlatformsForMembers(signatures.flatMap { it.nodes }) + val platformsOnSignature = !samePlatforms(signatures.map { it.platforms }) + } + + + fun computePlatformLevel(): PlatformPlacement { + if (data.any { it.platformsOnSignature }) { + return PlatformPlacement.Signature + } + if (samePlatforms(data.map { it.platforms })) { + return PlatformPlacement.Row + } + return PlatformPlacement.Summary + } + val platformPlacement: PlatformPlacement = computePlatformLevel() + val platforms = effectivePlatformsForMembers(data.flatMap { it.signatures.flatMap { it.nodes } }) + + + enum class PlatformPlacement { + Row, Summary, Signature + } +} + abstract class StructuredOutputBuilder(val to: StringBuilder, val location: Location, val generator: NodeLocationAwareGenerator, val languageService: LanguageService, val extension: String, - val impliedPlatforms: List<String>) : FormattedOutputBuilder { - - protected fun DocumentationNode.location() = generator.location(this) + impliedPlatforms: List<String>) : FormattedOutputBuilder { protected fun wrap(prefix: String, suffix: String, body: () -> Unit) { to.append(prefix) @@ -69,9 +110,13 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, abstract fun appendText(text: String) open fun appendSinceKotlin(version: String) { - appendParagraph { - appendText("Available since Kotlin: ") + appendText("Since: ") appendCode { appendText(version) } + } + + open fun appendSinceKotlinWrapped(version: String) { + wrap(" (", ")") { + appendSinceKotlin(version) } } @@ -83,6 +128,14 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } } + open fun appendAsPlatformDependentBlock(platforms: PlatformsData, block: (PlatformsData) -> Unit) { + block(platforms) + } + + open fun appendAsSummaryGroup(platforms: PlatformsData, block: (PlatformsData) -> Unit) { + appendAsPlatformDependentBlock(platforms, block) + } + open fun appendSymbol(text: String) { appendText(text) } @@ -95,6 +148,10 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendText(text) } + open fun appendAsNodeDescription(platforms: PlatformsData, block: () -> Unit) { + block() + } + fun appendEntity(text: String) { to.append(text) } @@ -158,7 +215,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } is ContentNodeLink -> { val node = content.node - val linkTo = if (node != null) locationHref(location, node) else "#" + val linkTo = if (node != null) locationHref(location, node, generator) else "#" appendLinkIfNotThisPage(linkTo, content) } is ContentExternalLink -> appendLinkIfNotThisPage(content.href, content) @@ -173,14 +230,14 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, content as ContentBlockCode fun ContentBlockCode.appendBlockCodeContent() { children - .dropWhile { it is ContentText && it.text.isBlank() } - .forEach { appendContent(it) } + .dropWhile { it is ContentText && it.text.isBlank() } + .forEach { appendContent(it) } } when (content) { is ContentBlockSampleCode -> - appendSampleBlockCode(content.language, content.importsBlock::appendBlockCodeContent, { content.appendBlockCodeContent() }) + appendSampleBlockCode(content.language, content.importsBlock::appendBlockCodeContent) { content.appendBlockCodeContent() } is ContentBlockCode -> - appendBlockCode(content.language, { content.appendBlockCodeContent() }) + appendBlockCode(content.language) { content.appendBlockCodeContent() } } } is ContentHeading -> appendHeader(content.level) { appendContent(content.children) } @@ -196,51 +253,42 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } } - open fun link(from: DocumentationNode, - to: DocumentationNode, - name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink = link(from, to, extension, name) - - open fun link(from: DocumentationNode, - to: DocumentationNode, - extension: String, - name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink { - if (to.owner?.kind == NodeKind.GroupNode) - return link(from, to.owner!!, extension, name) - - if (from.owner?.kind == NodeKind.GroupNode) - return link(from.owner!!, to, extension, name) - - return FormatLink(name(to), from.location().relativePathTo(to.location())) - } + open fun link( + from: DocumentationNode, + to: DocumentationNode, + name: (DocumentationNode) -> String = DocumentationNode::name + ): FormatLink = link(from, to, extension, name) - fun locationHref(from: Location, to: DocumentationNode): String { - val topLevelPage = to.references(RefKind.TopLevelPage).singleOrNull()?.to - if (topLevelPage != null) { - val signature = to.detailOrNull(NodeKind.Signature) - return from.relativePathTo(topLevelPage.location(), signature?.name ?: to.name) - } - return from.relativePathTo(to.location()) - } + open fun link( + from: DocumentationNode, + to: DocumentationNode, + extension: String, + name: (DocumentationNode) -> String = DocumentationNode::name + ): FormatLink = + FormatLink(name(to), generator.relativePathToLocation(from, to)) private fun DocumentationNode.isModuleOrPackage(): Boolean = - kind == NodeKind.Module || kind == NodeKind.Package + kind == NodeKind.Module || kind == NodeKind.Package protected open fun appendAsSignature(node: ContentNode, block: () -> Unit) { block() } - protected open fun appendAsOverloadGroup(to: StringBuilder, platforms: Set<String>, block: () -> Unit) { + protected open fun appendAsOverloadGroup(to: StringBuilder, platforms: PlatformsData, block: () -> Unit) { block() } - protected open fun appendIndexRow(platforms: Set<String>, block: () -> Unit) { + protected open fun appendIndexRow(platforms: PlatformsData, block: () -> Unit) { appendTableRow(block) } - protected open fun appendPlatforms(platforms: Set<String>) { + protected open fun appendPlatformsAsText(platforms: PlatformsData) { + appendPlatforms(platforms) + } + + protected open fun appendPlatforms(platforms: PlatformsData) { if (platforms.isNotEmpty()) { - appendLine() - appendText(platforms.joinToString(prefix = "(", postfix = ")")) + appendText(platforms.keys.joinToString(prefix = "(", postfix = ") ")) } } @@ -254,7 +302,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> = - sections.filter { it.subjectName != null }.groupBy { it.tag } + sections.filter { it.subjectName != null }.groupBy { it.tag } private fun ContentNode.appendSignature() { if (this is ContentBlock && this.isEmpty()) { @@ -289,7 +337,6 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, val packageName = if (singleNode.name.isEmpty()) "<root>" else singleNode.name appendHeader(2) { appendText("Package $packageName") } } - singleNode.appendPlatforms() appendContent(singleNode.content) } else { val breakdownByName = nodes.groupBy { node -> node.name } @@ -302,45 +349,134 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } private fun appendDocumentation(overloads: Iterable<DocumentationNode>, isSingleNode: Boolean) { - val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content } + val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> + when (node.kind) { + NodeKind.GroupNode -> node.origins.map { it.content } + else -> node.content + } + } if (breakdownBySummary.size == 1) { - formatOverloadGroup(breakdownBySummary.values.single(), isSingleNode) + val node = breakdownBySummary.values.single() + appendAsNodeDescription(effectivePlatformsForMembers(node)) { + formatOverloadGroup(node, isSingleNode) + } } else { for ((_, items) in breakdownBySummary) { - - appendAsOverloadGroup(to, platformsOfItems(items)) { + appendAsOverloadGroup(to, effectivePlatformsForMembers(items)) { formatOverloadGroup(items) } - } } } private fun formatOverloadGroup(items: List<DocumentationNode>, isSingleNode: Boolean = false) { + + val platformsPerGroup = samePlatforms( + items.flatMap { + if (it.kind == NodeKind.GroupNode) { + it.origins.groupBy { origin -> + languageService.render(origin) + }.values.map { origins -> effectivePlatformsForMembers(origins) } + } else { + listOf(effectivePlatformsForNode(it)) + } + } + ) + + if (platformsPerGroup) { + appendAsPlatformDependentBlock(effectivePlatformsForMembers(items)) { platforms -> + appendPlatforms(platforms) + } + } for ((index, item) in items.withIndex()) { if (index > 0) appendLine() + + if (item.kind == NodeKind.GroupNode) { + renderGroupNode(item, isSingleNode, !platformsPerGroup) + } else { + renderSimpleNode(item, isSingleNode, !platformsPerGroup) + } + + } + // All items have exactly the same documentation, so we can use any item to render it + val item = items.first() + // TODO: remove this block cause there is no one node with OverloadGroupNote detail + item.details(NodeKind.OverloadGroupNote).forEach { + appendContent(it.content) + } + + if (item.kind == NodeKind.GroupNode) { + val groupByContent = item.origins.groupBy { it.content } + if (groupByContent.count { !it.key.isEmpty() } > 1) { + if (groupByContent.size > 1) println("[mult] Found ov diff: ${generator.location(item).path}") + } + for ((content, origins) in groupByContent) { + if (content.isEmpty()) continue + appendAsPlatformDependentBlock(effectivePlatformsForMembers(origins)) { platforms -> + if (groupByContent.count { !it.key.isEmpty() } > 1) { + appendPlatformsAsText(platforms) + } + appendContent(content.summary) + content.appendDescription() + } + } + } else { + val platforms = effectivePlatformsForNode(item) + appendAsPlatformDependentBlock(platforms) { + appendContent(item.summary) + item.content.appendDescription() + } + } + } + + + fun renderSimpleNode(item: DocumentationNode, isSingleNode: Boolean, withPlatforms: Boolean = true) { + appendAsPlatformDependentBlock(effectivePlatformsForMembers(listOf(item))) { platforms -> + // TODO: use summarizesignatures val rendered = languageService.render(item) item.detailOrNull(NodeKind.Signature)?.let { if (item.kind !in NodeKind.classLike || !isSingleNode) appendAnchor(it.name) } + if (withPlatforms) { + appendPlatforms(platforms) + } appendAsSignature(rendered) { appendCode { appendContent(rendered) } item.appendSourceLink() } item.appendOverrides() item.appendDeprecation() - item.appendPlatforms() } - // All items have exactly the same documentation, so we can use any item to render it - val item = items.first() - item.details(NodeKind.OverloadGroupNote).forEach { - appendContent(it.content) + } + + fun renderGroupNode(item: DocumentationNode, isSingleNode: Boolean, withPlatforms: Boolean = true) { + // TODO: use summarizesignatures + val groupBySignature = item.origins.groupBy { + languageService.render(it) } - appendContent(item.content.summary) - item.appendDescription() + for ((sign, nodes) in groupBySignature) { + appendAsPlatformDependentBlock(effectivePlatformsForMembers(nodes)) { platforms -> + val first = nodes.first() + first.detailOrNull(NodeKind.Signature)?.let { + if (item.kind !in NodeKind.classLike || !isSingleNode) + appendAnchor(it.name) + } + + if (withPlatforms) { + appendPlatforms(platforms) + } + + appendAsSignature(sign) { + appendCode { appendContent(sign) } + } + first.appendOverrides() + first.appendDeprecation() + } + + } } private fun DocumentationNode.appendSourceLink() { @@ -355,7 +491,7 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, overrides.forEach { appendParagraph { to.append("Overrides ") - val location = location().relativePathTo(it.location()) + val location = generator.relativePathToLocation(this, it) appendLink(FormatLink(it.owner!!.name + "." + it.name, location)) } @@ -367,82 +503,95 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, val deprecationParameter = deprecation!!.details(NodeKind.Parameter).firstOrNull() val deprecationValue = deprecationParameter?.details(NodeKind.Value)?.firstOrNull() appendLine() - if (deprecationValue != null) { - appendStrong { to.append("Deprecated:") } - appendText(" " + deprecationValue.name.removeSurrounding("\"")) - appendLine() - appendLine() - } else if (deprecation?.content != Content.Empty) { - appendStrong { to.append("Deprecated:") } - to.append(" ") - appendContent(deprecation!!.content) - } else { - appendStrong { to.append("Deprecated") } - appendLine() - appendLine() - } - } - } - - private fun DocumentationNode.appendPlatforms() { - val platforms = if (isModuleOrPackage()) - platformsToShow.toSet() + platformsOfItems(members) - else - platformsToShow - - if (platforms.isEmpty()) return - - appendParagraph { - appendStrong { to.append("Platform and version requirements:") } - to.append(" " + platforms.joinToString()) - } - } - - protected fun platformsOfItems(items: List<DocumentationNode>): Set<String> { - val platforms = items.asSequence().map { - when (it.kind) { - NodeKind.ExternalClass, NodeKind.Package, NodeKind.Module, NodeKind.GroupNode -> platformsOfItems(it.members) - else -> it.platformsToShow.toSet() - } - } - - fun String.isKotlinVersion() = this.startsWith("Kotlin") - - // Calculating common platforms for items - return platforms.reduce { result, platformsOfItem -> - val otherKotlinVersion = result.find { it.isKotlinVersion() } - val (kotlinVersions, otherPlatforms) = platformsOfItem.partition { it.isKotlinVersion() } - - // When no Kotlin version specified, it means that version is 1.0 - if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) { - val allKotlinVersions = (kotlinVersions + otherKotlinVersion).distinct() - - val minVersion = allKotlinVersions.min()!! - val resultVersion = when { - allKotlinVersions.size == 1 -> allKotlinVersions.single() - minVersion.endsWith("+") -> minVersion - else -> minVersion + "+" + when { + deprecationValue != null -> { + appendStrong { to.append("Deprecated:") } + appendText(" " + deprecationValue.name.removeSurrounding("\"")) + appendLine() + appendLine() + } + deprecation?.content != Content.Empty -> { + appendStrong { to.append("Deprecated:") } + to.append(" ") + appendContent(deprecation!!.content) + } + else -> { + appendStrong { to.append("Deprecated") } + appendLine() + appendLine() } - - result.intersect(otherPlatforms) + resultVersion - } else { - result.intersect(platformsOfItem) } } } - val DocumentationNode.platformsToShow: List<String> - get() = platforms.let { if (it.containsAll(impliedPlatforms)) it - impliedPlatforms else it } - private fun DocumentationNode.appendDescription() { - if (content.description != ContentEmpty) { - appendContent(content.description) - } - content.getSectionsWithSubjects().forEach { +// protected fun platformsOfItems(items: List<DocumentationNode>): Set<String> { +// val platforms = items.asSequence().map { +// when (it.kind) { +// NodeKind.ExternalClass, NodeKind.Package, NodeKind.Module -> platformsOfItems(it.members) +// NodeKind.GroupNode -> platformsOfItems(it.origins) +// else -> it.platformsToShow.toSet() +// } +// } +// +// fun String.isKotlinVersion() = this.startsWith("Kotlin") +// +// if (platforms.count() == 0) return emptySet() +// +// // Calculating common platforms for items +// return platforms.reduce { result, platformsOfItem -> +// val otherKotlinVersion = result.find { it.isKotlinVersion() } +// val (kotlinVersions, otherPlatforms) = platformsOfItem.partition { it.isKotlinVersion() } +// +// // When no Kotlin version specified, it means that version is 1.0 +// if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) { +// result.intersect(platformsOfItem) + mergeVersions(otherKotlinVersion, kotlinVersions) +// } else { +// result.intersect(platformsOfItem) +// } +// } +// } +// +// protected fun unionPlatformsOfItems(items: List<DocumentationNode>): Set<String> { +// val platforms = items.asSequence().map { +// when (it.kind) { +// NodeKind.GroupNode -> unionPlatformsOfItems(it.origins) +// else -> it.platformsToShow.toSet() +// } +// } +// +// fun String.isKotlinVersion() = this.startsWith("Kotlin") +// +// if (platforms.count() == 0) return emptySet() +// +// // Calculating common platforms for items +// return platforms.reduce { result, platformsOfItem -> +// val otherKotlinVersion = result.find { it.isKotlinVersion() } +// val (kotlinVersions, otherPlatforms) = platformsOfItem.partition { it.isKotlinVersion() } +// +// // When no Kotlin version specified, it means that version is 1.0 +// if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) { +// result.union(otherPlatforms) + mergeVersions(otherKotlinVersion, kotlinVersions) +// } else { +// result.union(otherPlatforms) +// } +// } +// } + +// val DocumentationNode.platformsToShow: List<String> +// get() = platforms + + private fun Content.appendDescription() { + if (description != ContentEmpty) { + appendContent(description) + } + + + getSectionsWithSubjects().forEach { appendSectionWithSubject(it.key, it.value) } - for (section in content.sections.filter { it.subjectName == null }) { + for (section in sections.filter { it.subjectName == null }) { appendSectionWithTag(section) } } @@ -461,6 +610,38 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } } } + + fun appendOriginsGroupByContent(node: DocumentationNode) { + require(node.kind == NodeKind.GroupNode) + val groupByContent = + node.origins.groupBy { it.content } + .mapValues { (_, origins) -> + effectivePlatformsForMembers(origins) + } + .filterNot { it.key.isEmpty() } + .toList() + .sortedByDescending { it.second.size } + + if (groupByContent.size > 1) println("[mult] Found diff: ${generator.location(node).path}") + for ((content, platforms) in groupByContent) { + appendAsPlatformDependentBlock(platforms) { + if (groupByContent.size > 1) { + appendPlatformsAsText(platforms) + } + appendContent(content.summary) + content.appendDescription() + } + } + } + } + + inner class SingleNodePageBuilder(val node: DocumentationNode, noHeader: Boolean = false) : + PageBuilder(listOf(node), noHeader) { + + override fun build() { + super.build() + SectionsBuilder(node).build() + } } inner class GroupNodePageBuilder(val node: DocumentationNode) : PageBuilder(listOf(node)) { @@ -473,39 +654,37 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendLine() appendHeader { appendText(node.name) } - fun DocumentationNode.priority(): Int = when (kind) { - NodeKind.TypeAlias -> 1 - NodeKind.Class -> 2 - else -> 3 - } - - for (member in node.members.sortedBy(DocumentationNode::priority)) { - - appendAsOverloadGroup(to, platformsOfItems(listOf(member))) { - formatSubNodeOfGroup(member) - } + appendAsNodeDescription(effectivePlatformsForNode(node)) { + renderGroupNode(node, true) + appendOriginsGroupByContent(node) } - } - fun formatSubNodeOfGroup(member: DocumentationNode) { - SingleNodePageBuilder(member, true).build() + SectionsBuilder(node).build() } } - - inner class SingleNodePageBuilder(val node: DocumentationNode, noHeader: Boolean = false) - : PageBuilder(listOf(node), noHeader) { - +// +// private fun unionPlatformsOfItems(items: List<DocumentationNode>): Set<String> { +// val platforms = items.flatMapTo(mutableSetOf<String>()) { +// when (it.kind) { +// NodeKind.GroupNode -> unionPlatformsOfItems(it.origins) +// else -> it.platforms +// } +// } +// +// return platforms +// } + + + inner class SectionsBuilder(val node: DocumentationNode): PageBuilder(listOf(node)) { override fun build() { - super.build() - if (node.kind == NodeKind.ExternalClass) { appendSection("Extensions for ${node.name}", node.members) return } fun DocumentationNode.membersOrGroupMembers(predicate: (DocumentationNode) -> Boolean): List<DocumentationNode> { - return members.filter(predicate) + members(NodeKind.GroupNode).flatMap { it.members.filter(predicate) } + return members.filter(predicate) + members(NodeKind.GroupNode).filter{ it.origins.isNotEmpty() && predicate(it.origins.first()) } } fun DocumentationNode.membersOrGroupMembers(kind: NodeKind): List<DocumentationNode> { @@ -513,40 +692,39 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } appendSection("Packages", node.members(NodeKind.Package), platformsBasedOnMembers = true) - appendSection("Types", node.membersOrGroupMembers { it.kind in NodeKind.classLike && it.kind != NodeKind.TypeAlias && it.kind != NodeKind.AnnotationClass && it.kind != NodeKind.Exception }) + appendSection("Types", node.membersOrGroupMembers { it.kind in NodeKind.classLike /*&& it.kind != NodeKind.TypeAlias*/ && it.kind != NodeKind.AnnotationClass && it.kind != NodeKind.Exception }) appendSection("Annotations", node.membersOrGroupMembers(NodeKind.AnnotationClass)) appendSection("Exceptions", node.membersOrGroupMembers(NodeKind.Exception)) - appendSection("Type Aliases", node.membersOrGroupMembers(NodeKind.TypeAlias)) appendSection("Extensions for External Classes", node.members(NodeKind.ExternalClass)) - appendSection("Enum Values", node.members(NodeKind.EnumItem), sortMembers = false, omitSamePlatforms = true) - appendSection("Constructors", node.members(NodeKind.Constructor), omitSamePlatforms = true) - appendSection("Properties", node.members(NodeKind.Property), omitSamePlatforms = true) + appendSection("Enum Values", node.membersOrGroupMembers(NodeKind.EnumItem), sortMembers = false, omitSamePlatforms = true) + appendSection("Constructors", node.membersOrGroupMembers(NodeKind.Constructor), omitSamePlatforms = true) + appendSection("Properties", node.membersOrGroupMembers(NodeKind.Property), omitSamePlatforms = true) appendSection("Inherited Properties", node.inheritedMembers(NodeKind.Property)) - appendSection("Functions", node.members(NodeKind.Function), omitSamePlatforms = true) + appendSection("Functions", node.membersOrGroupMembers(NodeKind.Function), omitSamePlatforms = true) appendSection("Inherited Functions", node.inheritedMembers(NodeKind.Function)) - appendSection("Companion Object Properties", node.members(NodeKind.CompanionObjectProperty), omitSamePlatforms = true) + appendSection("Companion Object Properties", node.membersOrGroupMembers(NodeKind.CompanionObjectProperty), omitSamePlatforms = true) appendSection("Inherited Companion Object Properties", node.inheritedCompanionObjectMembers(NodeKind.Property)) - appendSection("Companion Object Functions", node.members(NodeKind.CompanionObjectFunction), omitSamePlatforms = true) + appendSection("Companion Object Functions", node.membersOrGroupMembers(NodeKind.CompanionObjectFunction), omitSamePlatforms = true) appendSection("Inherited Companion Object Functions", node.inheritedCompanionObjectMembers(NodeKind.Function)) appendSection("Other members", node.members.filter { it.kind !in setOf( - NodeKind.Class, - NodeKind.Interface, - NodeKind.Enum, - NodeKind.Object, - NodeKind.AnnotationClass, - NodeKind.Exception, - NodeKind.TypeAlias, - NodeKind.Constructor, - NodeKind.Property, - NodeKind.Package, - NodeKind.Function, - NodeKind.CompanionObjectProperty, - NodeKind.CompanionObjectFunction, - NodeKind.ExternalClass, - NodeKind.EnumItem, - NodeKind.AllTypes, - NodeKind.GroupNode + NodeKind.Class, + NodeKind.Interface, + NodeKind.Enum, + NodeKind.Object, + NodeKind.AnnotationClass, + NodeKind.Exception, + NodeKind.TypeAlias, + NodeKind.Constructor, + NodeKind.Property, + NodeKind.Package, + NodeKind.Function, + NodeKind.CompanionObjectProperty, + NodeKind.CompanionObjectFunction, + NodeKind.ExternalClass, + NodeKind.EnumItem, + NodeKind.AllTypes, + NodeKind.GroupNode ) }) @@ -556,12 +734,12 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendSection("Companion Object Extension Properties", allExtensions.filter { it.kind == NodeKind.CompanionObjectProperty }) appendSection("Companion Object Extension Functions", allExtensions.filter { it.kind == NodeKind.CompanionObjectFunction }) appendSection("Inheritors", - node.inheritors.filter { it.kind != NodeKind.EnumItem }) + node.inheritors.filter { it.kind != NodeKind.EnumItem }) if (node.kind == NodeKind.Module) { appendHeader(3) { to.append("Index") } node.members(NodeKind.AllTypes).singleOrNull()?.let { allTypes -> - appendLink(link(node, allTypes, { "All Types" })) + appendLink(link(node, allTypes) { "All Types" }) } } } @@ -581,27 +759,39 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendTable("Name", "Summary") { appendTableBody { - for ((memberLocation, members) in membersMap) { - val elementPlatforms = platformsOfItems(members, omitSamePlatforms) - val platforms = if (platformsBasedOnMembers) - members.flatMapTo(mutableSetOf()) { platformsOfItems(it.members) } + elementPlatforms - else - elementPlatforms + for ((memberLocation, membersList) in membersMap) { + val platforms = effectivePlatformsForMembers(membersList) +// val platforms = if (platformsBasedOnMembers) +// members.flatMapTo(mutableSetOf()) { platformsOfItems(it.members) } + elementPlatforms +// else +// elementPlatforms + + val summarized = computeSummarySignatures(membersList) + appendIndexRow(platforms) { appendTableCell { - appendParagraph { + if (summarized.platformPlacement == Summarized.PlatformPlacement.Row) { + appendPlatforms(platforms) + } + appendHeader(level = 4) { + // appendParagraph { appendLink(memberLocation) - if (members.singleOrNull()?.kind != NodeKind.ExternalClass) { - appendPlatforms(platforms) - } } + if (node.sinceKotlin != null) { + appendSinceKotlin(node.sinceKotlin.toString()) + } + + if (membersList.singleOrNull()?.sinceKotlin != null){ + appendSinceKotlinWrapped(membersList.single().sinceKotlin.toString()) + } +// } +// if (members.singleOrNull()?.kind != NodeKind.ExternalClass) { +// appendPlatforms(platforms) +// } +// } } appendTableCell { - val breakdownBySummary = members.groupBy { it.summary } - for ((summary, items) in breakdownBySummary) { - appendSummarySignatures(items) - appendContent(summary) - } + appendSummarySignatures(summarized) } } } @@ -609,35 +799,94 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, } } - private fun platformsOfItems(items: List<DocumentationNode>, omitSamePlatforms: Boolean = true): Set<String> { - val platforms = platformsOfItems(items) - if (platforms.isNotEmpty() && (platforms != node.platformsToShow.toSet() || !omitSamePlatforms)) { - return platforms +// +// private fun platformsOfItems(items: List<DocumentationNode>, omitSamePlatforms: Boolean = true): Set<String> { +// if (items.all { it.kind != NodeKind.Package && it.kind != NodeKind.Module && it.kind != NodeKind.ExternalClass }) { +// return unionPlatformsOfItems(items) +// } +// +// val platforms = platformsOfItems(items) +// if (platforms.isNotEmpty() && (platforms != node.platformsToShow.toSet() || !omitSamePlatforms)) { +// return platforms +// } +// return emptySet() +// } + + + + private fun computeSummarySignatures(items: List<DocumentationNode>): Summarized = + Summarized(items.groupBy { it.summary }.mapValues { (_, nodes) -> + val nodesToAppend = nodes.flatMap { if(it.kind == NodeKind.GroupNode) it.origins else listOf(it) } + + val summarySignature = languageService.summarizeSignatures(nodesToAppend) + if (summarySignature != null) { + mapOf(summarySignature to nodesToAppend) + } else { + nodesToAppend.groupBy { + languageService.render(it, RenderMode.SUMMARY) + } + } + }) + + + private fun appendSummarySignatures( + summarized: Summarized + ) { + for(summary in summarized.data) { + + appendAsSummaryGroup(summary.platforms) { + if (summarized.platformPlacement == Summarized.PlatformPlacement.Summary) { + appendPlatforms(summary.platforms) + } + appendContent(summary.content) + summary.signatures.subList(0, summary.signatures.size - 1).forEach { + appendSignatures( + it, + summarized.platformPlacement == Summarized.PlatformPlacement.Signature + ) + appendLine() + } + appendSignatures( + summary.signatures.last(), + summarized.platformPlacement == Summarized.PlatformPlacement.Signature + ) + } + } - return emptySet() } - private fun appendSummarySignatures(items: List<DocumentationNode>) { - val summarySignature = languageService.summarizeSignatures(items) - if (summarySignature != null) { - appendAsSignature(summarySignature) { - summarySignature.appendSignature() + private fun appendSignatures( + signature: Summarized.SummarizedNodes, + withPlatforms: Boolean + ) { + +// val platforms = if (platformsBasedOnMembers) +// items.flatMapTo(mutableSetOf()) { platformsOfItems(it.members) } + elementPlatforms +// else +// elementPlatforms + + + appendAsPlatformDependentBlock(signature.platforms) { + if (withPlatforms) { + appendPlatforms(signature.platforms) } - return - } - val renderedSignatures = items.map { languageService.render(it, RenderMode.SUMMARY) } - renderedSignatures.subList(0, renderedSignatures.size - 1).forEach { - appendAsSignature(it) { - it.appendSignature() + appendAsSignature(signature.content) { + signature.content.appendSignature() } - appendLine() - } - appendAsSignature(renderedSignatures.last()) { - renderedSignatures.last().appendSignature() + appendSoftLineBreak() } } } + private fun DocumentationNode.isClassLikeGroupNode(): Boolean { + if (kind != NodeKind.GroupNode) { + return false + } + + return origins.all { it.kind in NodeKind.classLike } + } + + inner class AllTypesNodeBuilder(val node: DocumentationNode) : PageBuilder(listOf(node)) { @@ -648,21 +897,23 @@ abstract class StructuredOutputBuilder(val to: StringBuilder, appendTable("Name", "Summary") { appendTableBody { for (type in node.members) { - appendTableRow { - appendTableCell { + val platforms = effectivePlatformsForNode(type) + appendIndexRow(platforms) { + appendPlatforms(platforms) + appendHeader(level = 5) { appendLink(link(node, type) { if (it.kind == NodeKind.ExternalClass) it.name else it.qualifiedName() }) - if (type.kind == NodeKind.ExternalClass) { - val packageName = type.owner?.name - if (packageName != null) { - appendText(" (extensions in package $packageName)") - } - } } - appendTableCell { - appendContent(type.summary) + + if (type.kind == NodeKind.ExternalClass) { + val packageName = type.owner?.name + if (packageName != null) { + appendText(" (extensions in package $packageName)") + } } + + appendContent(type.summary) } } } @@ -687,3 +938,78 @@ abstract class StructuredFormatService(val generator: NodeLocationAwareGenerator override final val linkExtension: String = extension) : FormatService { } + +typealias PlatformsData = Map<String, Set<DocumentationNode>> + +fun memberPlatforms(node: DocumentationNode): PlatformsData { + val members = when { + node.kind == NodeKind.GroupNode -> node.origins + node.kind in NodeKind.classLike -> emptyList() + node.kind in NodeKind.memberLike -> emptyList() + else -> node.members + } + + return members.map(::effectivePlatformsForNode).fold(mapOf(), ::mergePlatforms) +} + +fun mergePlatforms(a: PlatformsData, b: PlatformsData): PlatformsData { + val mutable = a.toMutableMap() + b.forEach { (name, declarations) -> + mutable.merge(name, declarations) { a, b -> a.union(b) } + } + return mutable +} + +fun effectivePlatformsForNode(node: DocumentationNode): PlatformsData { + val platforms = node.platforms + memberPlatforms(node).keys + return platforms.keysToMap { setOf(node) } +} + +fun effectivePlatformsForMembers(nodes: Collection<DocumentationNode>): PlatformsData { + return nodes.map { effectivePlatformsForNode(it) }.reduce(::mergePlatforms) +} + +fun mergeVersions(kotlinVersions: List<String>): String { + return kotlinVersions.distinct().min().orEmpty() +} + +fun effectiveSinceKotlinForNode(node: DocumentationNode, baseVersion: String = "1.0"): String { + val members = when { + node.kind == NodeKind.GroupNode -> node.origins + node.kind in NodeKind.classLike -> emptyList() + node.kind in NodeKind.memberLike -> emptyList() + else -> node.members + } + val newBase = node.sinceKotlin ?: baseVersion + val memberVersion = if (members.isNotEmpty()) effectiveSinceKotlinForNodes(members, newBase) else newBase + + return node.sinceKotlin ?: memberVersion +} + +fun effectiveSinceKotlinForNodes(nodes: Collection<DocumentationNode>, baseVersion: String = "1.0"): String { + val map = nodes.map { effectiveSinceKotlinForNode(it, baseVersion) } + return mergeVersions(map) +} + +fun samePlatforms(platformsPerNode: Collection<PlatformsData>): Boolean { + + val first = platformsPerNode.firstOrNull()?.keys ?: return true + return platformsPerNode.all { it.keys == first } +} + +fun locationHref( + from: Location, + to: DocumentationNode, + generator: NodeLocationAwareGenerator, + pathOnly: Boolean = false +): String { + val topLevelPage = to.references(RefKind.TopLevelPage).singleOrNull()?.to + if (topLevelPage != null) { + val signature = to.detailOrNull(NodeKind.Signature) + return from.relativePathTo( + generator.location(topLevelPage), + (signature?.name ?: to.name).takeUnless { pathOnly } + ) + } + return from.relativePathTo(generator.location(to)) +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Formats/YamlOutlineService.kt b/core/src/main/kotlin/Formats/YamlOutlineService.kt index c36f98eb..3c92d8ff 100644 --- a/core/src/main/kotlin/Formats/YamlOutlineService.kt +++ b/core/src/main/kotlin/Formats/YamlOutlineService.kt @@ -13,7 +13,7 @@ class YamlOutlineService @Inject constructor( override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) { val indent = " ".repeat(outlineLevel) to.appendln("$indent- title: ${languageService.renderName(node)}") - to.appendln("$indent url: ${generator.location(node).path}") + to.appendln("$indent url: ${generator.relativePathToLocation(node.path.first(), node)}") } override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) { diff --git a/core/src/main/kotlin/Generation/DocumentationMerger.kt b/core/src/main/kotlin/Generation/DocumentationMerger.kt new file mode 100644 index 00000000..5d53868a --- /dev/null +++ b/core/src/main/kotlin/Generation/DocumentationMerger.kt @@ -0,0 +1,229 @@ +package org.jetbrains.dokka.Generation + +import org.jetbrains.dokka.* + +class DocumentationMerger( + private val documentationModules: List<DocumentationModule>, + val logger: DokkaLogger +) { + private val producedNodeRefGraph: NodeReferenceGraph = NodeReferenceGraph() + private val signatureMap: Map<DocumentationNode, String> + private val oldToNewNodeMap: MutableMap<DocumentationNode, DocumentationNode> = mutableMapOf() + + init { + if (documentationModules.groupBy { it.name }.size > 1) { + throw IllegalArgumentException("Modules should have similar names: ${documentationModules.joinToString(", ") {it.name}}") + } + + signatureMap = documentationModules + .flatMap { it.nodeRefGraph.nodeMapView.entries } + .associate { (k, v) -> v to k } + + + documentationModules.map { it.nodeRefGraph } + .flatMap { it.references } + .forEach { producedNodeRefGraph.addReference(it) } + } + + private fun mergePackageReferences( + from: DocumentationNode, + packages: List<DocumentationReference> + ): List<DocumentationReference> { + val packagesByName = packages + .map { it.to } + .groupBy { it.name } + + val resultReferences = mutableListOf<DocumentationReference>() + for ((name, listOfPackages) in packagesByName) { + try { + val producedPackage = mergePackagesWithEqualNames(name, from, listOfPackages) + updatePendingReferences() + + resultReferences.add( + DocumentationReference(from, producedPackage, RefKind.Member) + ) + } catch (t: Throwable) { + val entries = listOfPackages.joinToString(",") { "references:${it.allReferences().size}" } + throw Error("Failed to merge package $name from $from with entries $entries. ${t.message}", t) + } + } + + return resultReferences + } + + private fun mergePackagesWithEqualNames( + name: String, + from: DocumentationNode, + packages: List<DocumentationNode> + ): DocumentationNode { + val mergedPackage = DocumentationNode(name, Content.Empty, NodeKind.Package) + + for (contentToAppend in packages.map { it.content }.distinct()) { + mergedPackage.updateContent { + for (otherChild in contentToAppend.children) { + children.add(otherChild) + } + } + } + + for (node in packages) { + oldToNewNodeMap[node] = mergedPackage + } + + val references = packages.flatMap { it.allReferences() } + val mergedReferences = mergeReferences(mergedPackage, references) + for (ref in mergedReferences) { + if (ref.kind == RefKind.Owner) { + continue + } + mergedPackage.addReference(ref) + } + + from.append(mergedPackage, RefKind.Member) + + return mergedPackage + } + + private fun mergeMemberGroupBy(it: DocumentationNode): String { + val signature = signatureMap[it] + + if (signature != null) { + return signature + } + + logger.error("Failed to find signature for $it in \n${it.allReferences().joinToString { "\n ${it.kind} ${it.to}" }}") + return "<ERROR>" + } + + private fun mergeMemberReferences( + from: DocumentationNode, + refs: List<DocumentationReference> + ): List<DocumentationReference> { + val membersBySignature: Map<String, List<DocumentationNode>> = refs.map { it.to } + .groupBy(this::mergeMemberGroupBy) + + val mergedMembers: MutableList<DocumentationReference> = mutableListOf() + for ((signature, members) in membersBySignature) { + val newNode = mergeMembersWithEqualSignature(signature, members) + + producedNodeRefGraph.register(signature, newNode) + updatePendingReferences() + from.append(newNode, RefKind.Member) + + mergedMembers.add(DocumentationReference(from, newNode, RefKind.Member)) + } + + return mergedMembers + } + + private fun mergeMembersWithEqualSignature( + signature: String, + nodes: List<DocumentationNode> + ): DocumentationNode { + require(nodes.isNotEmpty()) + + val singleNode = nodes.singleOrNull() + if (singleNode != null) { + singleNode.dropReferences { it.kind == RefKind.Owner } + return singleNode + } + + // Specialization processing + // Given (Common, JVM, JRE6, JS) and (JVM, JRE6) and (JVM, JRE7) + // Sorted: (JVM, JRE6), (JVM, JRE7), (Common, JVM, JRE6, JS) + // Should output: (JVM, JRE6), (JVM, JRE7), (Common, JS) + // Should not remove first platform + val nodesSortedByPlatformCount = nodes.sortedBy { it.platforms.size } + val allPlatforms = mutableSetOf<String>() + nodesSortedByPlatformCount.forEach { node -> + node.platforms + .filterNot { allPlatforms.add(it) } + .filter { it != node.platforms.first() } + .forEach { platform -> + node.dropReferences { it.kind == RefKind.Platform && it.to.name == platform } + } + } + + // TODO: Quick and dirty fox for merging extensions for external classes. Fix this probably in StructuredFormatService + // TODO: while refactoring documentation model + + val groupNode = if(nodes.first().kind == NodeKind.ExternalClass){ + DocumentationNode(nodes.first().name, Content.Empty, NodeKind.ExternalClass) + } else { + DocumentationNode(nodes.first().name, Content.Empty, NodeKind.GroupNode) + } + groupNode.appendTextNode(signature, NodeKind.Signature, RefKind.Detail) + + for (node in nodes) { + node.dropReferences { it.kind == RefKind.Owner } + groupNode.append(node, RefKind.Origin) + node.append(groupNode, RefKind.TopLevelPage) + + oldToNewNodeMap[node] = groupNode + } + + if (groupNode.kind == NodeKind.ExternalClass){ + val refs = nodes.flatMap { it.allReferences() }.filter { it.kind != RefKind.Owner && it.kind != RefKind.TopLevelPage } + refs.forEach { it.to.append(groupNode, RefKind.TopLevelPage); groupNode.append(it.to, RefKind.Member) } + } + + // if nodes are classes, nested members should be also merged and + // inserted at the same level with class + if (nodes.all { it.kind in NodeKind.classLike }) { + val members = nodes.flatMap { it.allReferences() }.filter { it.kind == RefKind.Member } + val mergedMembers = mergeMemberReferences(groupNode, members) + + for (ref in mergedMembers) { + if (ref.kind == RefKind.Owner) { + continue + } + + groupNode.append(ref.to, RefKind.Member) + } + } + + return groupNode + } + + + private fun mergeReferences( + from: DocumentationNode, + refs: List<DocumentationReference> + ): List<DocumentationReference> { + val (refsToPackages, otherRefs) = refs.partition { it.to.kind == NodeKind.Package } + val mergedPackages = mergePackageReferences(from, refsToPackages) + + val (refsToMembers, refsNotToMembers) = otherRefs.partition { it.kind == RefKind.Member } + val mergedMembers = mergeMemberReferences(from, refsToMembers) + + return mergedPackages + mergedMembers + refsNotToMembers + } + + fun merge(): DocumentationModule { + val mergedDocumentationModule = DocumentationModule( + name = documentationModules.first().name, + content = documentationModules.first().content, + nodeRefGraph = producedNodeRefGraph + ) + + val refs = documentationModules.flatMap { + it.allReferences() + } + mergeReferences(mergedDocumentationModule, refs) + + return mergedDocumentationModule + } + + private fun updatePendingReferences() { + for (ref in producedNodeRefGraph.references) { + ref.lazyNodeFrom.update() + ref.lazyNodeTo.update() + } + } + + private fun NodeResolver.update() { + if (this is NodeResolver.Exact && exactNode in oldToNewNodeMap) { + exactNode = oldToNewNodeMap[exactNode]!! + } + } +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Generation/DokkaGenerator.kt b/core/src/main/kotlin/Generation/DokkaGenerator.kt index a5279772..90d7cfcc 100644 --- a/core/src/main/kotlin/Generation/DokkaGenerator.kt +++ b/core/src/main/kotlin/Generation/DokkaGenerator.kt @@ -7,9 +7,10 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiManager -import org.jetbrains.dokka.DokkaConfiguration.SourceRoot +import org.jetbrains.dokka.Generation.DocumentationMerger import org.jetbrains.dokka.Utilities.DokkaAnalysisModule import org.jetbrains.dokka.Utilities.DokkaOutputModule +import org.jetbrains.dokka.Utilities.DokkaRunModule import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity @@ -18,63 +19,74 @@ 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.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.MemberDescriptor import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer import org.jetbrains.kotlin.resolve.TopDownAnalysisMode import org.jetbrains.kotlin.utils.PathUtil import java.io.File import kotlin.system.measureTimeMillis -class DokkaGenerator(val logger: DokkaLogger, - val classpath: List<String>, - val sources: List<SourceRoot>, - val samples: List<String>, - val includes: List<String>, - val moduleName: String, - val options: DocumentationOptions) { +class DokkaGenerator(val dokkaConfiguration: DokkaConfiguration, + val logger: DokkaLogger) { - private val documentationModule = DocumentationModule(moduleName) + private val documentationModules: MutableList<DocumentationModule> = mutableListOf() + private val globalInjector = Guice.createInjector(DokkaRunModule(dokkaConfiguration)) - fun generate() { - val sourcesGroupedByPlatform = sources.groupBy { it.platforms.firstOrNull() } - for ((platform, roots) in sourcesGroupedByPlatform) { - appendSourceModule(platform, roots) + + fun generate() = with(dokkaConfiguration) { + + + for (pass in passesConfigurations) { + val documentationModule = DocumentationModule(pass.moduleName) + appendSourceModule(pass, documentationModule) + documentationModules.add(documentationModule) } - documentationModule.prepareForGeneration(options) + + val totalDocumentationModule = DocumentationMerger(documentationModules, logger).merge() + totalDocumentationModule.prepareForGeneration(dokkaConfiguration) val timeBuild = measureTimeMillis { logger.info("Generating pages... ") - val outputInjector = Guice.createInjector(DokkaOutputModule(options, logger)) - outputInjector.getInstance(Generator::class.java).buildAll(documentationModule) + val outputInjector = globalInjector.createChildInjector(DokkaOutputModule(dokkaConfiguration, logger)) + val instance = outputInjector.getInstance(Generator::class.java) + instance.buildAll(totalDocumentationModule) } logger.info("done in ${timeBuild / 1000} secs") } - private fun appendSourceModule(defaultPlatform: String?, sourceRoots: List<SourceRoot>) { - val sourcePaths = sourceRoots.map { it.path } - val environment = createAnalysisEnvironment(sourcePaths) + private fun appendSourceModule( + passConfiguration: DokkaConfiguration.PassConfiguration, + documentationModule: DocumentationModule + ) = with(passConfiguration) { + + val sourcePaths = passConfiguration.sourceRoots.map { it.path } + val environment = createAnalysisEnvironment(sourcePaths, passConfiguration) logger.info("Module: $moduleName") - logger.info("Output: ${File(options.outputDir)}") + logger.info("Output: ${File(dokkaConfiguration.outputDir)}") logger.info("Sources: ${sourcePaths.joinToString()}") logger.info("Classpath: ${environment.classpath.joinToString()}") logger.info("Analysing sources and libraries... ") val startAnalyse = System.currentTimeMillis() - val defaultPlatformAsList = defaultPlatform?.let { listOf(it) }.orEmpty() + val defaultPlatformAsList = passConfiguration.targets val defaultPlatformsProvider = object : DefaultPlatformsProvider { override fun getDefaultPlatforms(descriptor: DeclarationDescriptor): List<String> { - val containingFilePath = descriptor.sourcePsi()?.containingFile?.virtualFile?.canonicalPath - ?.let { File(it).absolutePath } - val sourceRoot = containingFilePath?.let { path -> sourceRoots.find { path.startsWith(it.path) } } - return sourceRoot?.platforms ?: defaultPlatformAsList +// val containingFilePath = descriptor.sourcePsi()?.containingFile?.virtualFile?.canonicalPath +// ?.let { File(it).absolutePath } +// val sourceRoot = containingFilePath?.let { path -> sourceRoots.find { path.startsWith(it.path) } } + if (descriptor is MemberDescriptor && descriptor.isExpect) { + return defaultPlatformAsList.take(1) + } + return /*sourceRoot?.platforms ?: */defaultPlatformAsList } } - val injector = Guice.createInjector( - DokkaAnalysisModule(environment, options, defaultPlatformsProvider, documentationModule.nodeRefGraph, logger)) + val injector = globalInjector.createChildInjector( + DokkaAnalysisModule(environment, dokkaConfiguration, defaultPlatformsProvider, documentationModule.nodeRefGraph, passConfiguration, logger)) - buildDocumentationModule(injector, documentationModule, { isNotSample(it) }, includes) + buildDocumentationModule(injector, documentationModule, { isNotSample(it, passConfiguration.samples) }, includes) val timeAnalyse = System.currentTimeMillis() - startAnalyse logger.info("done in ${timeAnalyse / 1000} secs") @@ -82,26 +94,31 @@ class DokkaGenerator(val logger: DokkaLogger, Disposer.dispose(environment) } - fun createAnalysisEnvironment(sourcePaths: List<String>): AnalysisEnvironment { - val environment = AnalysisEnvironment(DokkaMessageCollector(logger)) + fun createAnalysisEnvironment( + sourcePaths: List<String>, + passConfiguration: DokkaConfiguration.PassConfiguration + ): AnalysisEnvironment { + val environment = AnalysisEnvironment(DokkaMessageCollector(logger), passConfiguration.analysisPlatform) environment.apply { - addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre()) + if (analysisPlatform == Platform.jvm) { + addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre()) + } // addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath()) - for (element in this@DokkaGenerator.classpath) { + for (element in passConfiguration.classpath) { addClasspath(File(element)) } addSources(sourcePaths) - addSources(this@DokkaGenerator.samples) + addSources(passConfiguration.samples) - loadLanguageVersionSettings(options.languageVersion, options.apiVersion) + loadLanguageVersionSettings(passConfiguration.languageVersion, passConfiguration.apiVersion) } return environment } - fun isNotSample(file: PsiFile): Boolean { + private fun isNotSample(file: PsiFile, samples: List<String>): Boolean { val sourceFile = File(file.virtualFile!!.path) return samples.none { sample -> val canonicalSample = File(sample).canonicalPath @@ -140,9 +157,7 @@ fun buildDocumentationModule(injector: Injector, val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzer::class.java) analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles) - val fragments = fragmentFiles - .map { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) } - .filterNotNull() + val fragments = fragmentFiles.mapNotNull { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) } .distinct() val packageDocs = injector.getInstance(PackageDocs::class.java) @@ -205,4 +220,4 @@ fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> { } } return result -} +}
\ No newline at end of file diff --git a/core/src/main/kotlin/Generation/FileGenerator.kt b/core/src/main/kotlin/Generation/FileGenerator.kt index b7c6cf63..ee2c068e 100644 --- a/core/src/main/kotlin/Generation/FileGenerator.kt +++ b/core/src/main/kotlin/Generation/FileGenerator.kt @@ -2,19 +2,52 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.google.inject.name.Named -import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import java.io.File -import java.io.FileOutputStream import java.io.IOException -import java.io.OutputStreamWriter +import java.io.PrintWriter +import java.io.StringWriter class FileGenerator @Inject constructor(@Named("outputDir") override val root: File) : NodeLocationAwareGenerator { @set:Inject(optional = true) var outlineService: OutlineFormatService? = null @set:Inject(optional = true) lateinit var formatService: FormatService - @set:Inject(optional = true) lateinit var options: DocumentationOptions + @set:Inject(optional = true) lateinit var dokkaConfiguration: DokkaConfiguration @set:Inject(optional = true) var packageListService: PackageListService? = null + private val createdFiles = mutableMapOf<File, List<String>>() + + private fun File.writeFileAndAssert(context: String, action: (File) -> Unit) { + //TODO: there is a possible refactoring to drop FileLocation + //TODO: aad File from API, Location#path. + //TODO: turn [Location] into a final class, + //TODO: Use [Location] all over the place without full + //TODO: reference to the real target path, + //TODO: it opens the way to safely track all files created + //TODO: to make sure no files were overwritten by mistake + //TODO: also, the NodeLocationAwareGenerator should be removed + + val writes = createdFiles.getOrDefault(this, listOf()) + context + createdFiles[this] = writes + if (writes.size > 1) { + println("ERROR. An attempt to write ${this.relativeTo(root)} several times!") + return + } + + try { + parentFile?.mkdirsOrFail() + action(this) + } catch (e : Throwable) { + println("Failed to write $this. ${e.message}") + e.printStackTrace() + } + } + + private fun File.mkdirsOrFail() { + if (!mkdirs() && !exists()) { + throw IOException("Failed to create directory $this") + } + } + override fun location(node: DocumentationNode): FileLocation { return FileLocation(fileForNode(node, formatService.linkExtension)) } @@ -23,45 +56,36 @@ class FileGenerator @Inject constructor(@Named("outputDir") override val root: F return File(root, relativePathToNode(node)).appendExtension(extension) } - fun locationWithoutExtension(node: DocumentationNode): FileLocation { + private fun locationWithoutExtension(node: DocumentationNode): FileLocation { return FileLocation(fileForNode(node)) } override fun buildPages(nodes: Iterable<DocumentationNode>) { for ((file, items) in nodes.groupBy { fileForNode(it, formatService.extension) }) { - - file.parentFile?.mkdirsOrFail() - try { - FileOutputStream(file).use { - OutputStreamWriter(it, Charsets.UTF_8).use { - it.write(formatService.format(location(items.first()), items)) - } - } - } catch (e: Throwable) { - println(e) + file.writeFileAndAssert("pages") { it -> + it.writeText(formatService.format(location(items.first()), items)) } - buildPages(items.flatMap { it.members }) + + buildPages(items.filterNot { it.kind == NodeKind.AllTypes }.flatMap { it.members }) } } override fun buildOutlines(nodes: Iterable<DocumentationNode>) { val outlineService = this.outlineService ?: return for ((location, items) in nodes.groupBy { locationWithoutExtension(it) }) { - val file = outlineService.getOutlineFileName(location) - file.parentFile?.mkdirsOrFail() - FileOutputStream(file).use { - OutputStreamWriter(it, Charsets.UTF_8).use { - it.write(outlineService.formatOutline(location, items)) - } + outlineService.getOutlineFileName(location).writeFileAndAssert("outlines") { file -> + file.writeText(outlineService.formatOutline(location, items)) } } } override fun buildSupportFiles() { formatService.enumerateSupportFiles { resource, targetPath -> - FileOutputStream(File(root, relativePathToNode(listOf(targetPath), false))).use { - javaClass.getResourceAsStream(resource).copyTo(it) + File(root, relativePathToNode(listOf(targetPath), false)).writeFileAndAssert("support files") { file -> + file.outputStream().use { + javaClass.getResourceAsStream(resource).copyTo(it) + } } } } @@ -74,16 +98,11 @@ class FileGenerator @Inject constructor(@Named("outputDir") override val root: F val moduleRoot = location(module).file.parentFile val packageListFile = File(moduleRoot, "package-list") - packageListFile.writeText("\$dokka.format:${options.outputFormat}\n" + - packageListService!!.formatPackageList(module as DocumentationModule)) - } + val text = "\$dokka.format:${dokkaConfiguration.format}\n" + packageListService!!.formatPackageList(module as DocumentationModule) + packageListFile.writeFileAndAssert("packages-list") { file -> + file.writeText(text) + } + } } - } - -private fun File.mkdirsOrFail() { - if (!mkdirs() && !exists()) { - throw IOException("Failed to create directory $this") - } -}
\ No newline at end of file diff --git a/core/src/main/kotlin/Generation/configurationImpl.kt b/core/src/main/kotlin/Generation/configurationImpl.kt deleted file mode 100644 index eecf122e..00000000 --- a/core/src/main/kotlin/Generation/configurationImpl.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.jetbrains.dokka - -import org.jetbrains.dokka.DokkaConfiguration.SourceLinkDefinition -import org.jetbrains.dokka.DokkaConfiguration.SourceRoot -import java.io.File - - -data class SourceLinkDefinitionImpl(override val path: String, - override val url: String, - override val lineSuffix: String?) : SourceLinkDefinition { - companion object { - fun parseSourceLinkDefinition(srcLink: String): SourceLinkDefinition { - val (path, urlAndLine) = srcLink.split('=') - return SourceLinkDefinitionImpl(File(path).canonicalPath, - urlAndLine.substringBefore("#"), - urlAndLine.substringAfter("#", "").let { if (it.isEmpty()) null else "#$it" }) - } - } -} - -class SourceRootImpl(path: String, override val platforms: List<String> = emptyList()) : SourceRoot { - override val path: String = File(path).absolutePath - - companion object { - fun parseSourceRoot(sourceRoot: String): SourceRoot { - val components = sourceRoot.split("::", limit = 2) - return SourceRootImpl(components.last(), if (components.size == 1) listOf() else components[0].split(',')) - } - } -} - -data class PackageOptionsImpl(override val prefix: String, - override val includeNonPublic: Boolean = false, - override val reportUndocumented: Boolean = true, - override val skipDeprecated: Boolean = false, - 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 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 f1f170d7..3b368329 100644 --- a/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt +++ b/core/src/main/kotlin/Java/JavaPsiDocumentationBuilder.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtModifierListOwner -import java.io.File fun getSignature(element: PsiElement?) = when(element) { is PsiPackage -> element.qualifiedName @@ -53,24 +52,24 @@ interface JavaDocumentationBuilder { } class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { - private val options: DocumentationOptions + private val passConfiguration: DokkaConfiguration.PassConfiguration private val refGraph: NodeReferenceGraph private val docParser: JavaDocumentationParser @Inject constructor( - options: DocumentationOptions, - refGraph: NodeReferenceGraph, - logger: DokkaLogger, - signatureProvider: ElementSignatureProvider, - externalDocumentationLinkResolver: ExternalDocumentationLinkResolver + passConfiguration: DokkaConfiguration.PassConfiguration, + refGraph: NodeReferenceGraph, + logger: DokkaLogger, + signatureProvider: ElementSignatureProvider, + externalDocumentationLinkResolver: ExternalDocumentationLinkResolver ) { - this.options = options + this.passConfiguration = passConfiguration this.refGraph = refGraph this.docParser = JavadocParser(refGraph, logger, signatureProvider, externalDocumentationLinkResolver) } - constructor(options: DocumentationOptions, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) { - this.options = options + constructor(passConfiguration: DokkaConfiguration.PassConfiguration, refGraph: NodeReferenceGraph, docParser: JavaDocumentationParser) { + this.passConfiguration = passConfiguration this.refGraph = refGraph this.docParser = docParser } @@ -152,7 +151,7 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { } } - private fun skipFile(javaFile: PsiJavaFile): Boolean = options.effectivePackageOptions(javaFile.packageName).suppress + private fun skipFile(javaFile: PsiJavaFile): Boolean = passConfiguration.effectivePackageOptions(javaFile.packageName).suppress private fun skipElement(element: Any) = skipElementByVisibility(element) || @@ -162,13 +161,13 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { private fun skipElementByVisibility(element: Any): Boolean = element is PsiModifierListOwner && element !is PsiParameter && - !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) && + !(passConfiguration.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) && (element.hasModifierProperty(PsiModifier.PRIVATE) || element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || element.isInternal()) private fun skipElementBySuppressedFiles(element: Any): Boolean = - element is PsiElement && File(element.containingFile.virtualFile.path).absoluteFile in options.suppressedFiles + element is PsiElement && element.containingFile.virtualFile.path in passConfiguration.suppressedFiles private fun PsiElement.isInternal(): Boolean { val ktElement = (this as? KtLightElement<*, *>)?.kotlinOrigin ?: return false @@ -317,7 +316,7 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { 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) + val packageNode = findOrCreatePackageNode(null, (psiClass.containingFile as PsiJavaFile).packageName, emptyMap(), refGraph) packageNode.append(new, RefKind.Member) return new } @@ -348,7 +347,7 @@ class JavaPsiDocumentationBuilder : JavaDocumentationBuilder { } fun hasSuppressDocTag(element: Any?): Boolean { - val declaration = (element as? KtLightDeclaration<*, *>)?.kotlinOrigin as? KtDeclaration ?: return false + val declaration = (element as? KtLightDeclaration<*, *>)?.kotlinOrigin ?: return false return PsiTreeUtil.findChildrenOfType(declaration.docComment, KDocTag::class.java).any { it.knownTag == KDocKnownTag.SUPPRESS } } diff --git a/core/src/main/kotlin/Java/JavadocParser.kt b/core/src/main/kotlin/Java/JavadocParser.kt index 70af73f9..25a974a3 100644 --- a/core/src/main/kotlin/Java/JavadocParser.kt +++ b/core/src/main/kotlin/Java/JavadocParser.kt @@ -192,7 +192,7 @@ class JavadocParser( return when { element.hasAttr("docref") -> { val docref = element.attr("docref") - ContentNodeLazyLink(docref, { -> refGraph.lookupOrWarn(docref, logger) }) + ContentNodeLazyLink(docref) { refGraph.lookupOrWarn(docref, logger)} } element.hasAttr("href") -> { val href = element.attr("href") @@ -234,9 +234,8 @@ class JavadocParser( linkSignature != null -> { val linkNode = ContentNodeLazyLink( - (tag.valueElement ?: linkElement).text, - { -> refGraph.lookupOrWarn(linkSignature, logger) } - ) + (tag.valueElement ?: linkElement).text + ) { refGraph.lookupOrWarn(linkSignature!!, logger) } linkNode.append(text) linkNode } diff --git a/core/src/main/kotlin/Kotlin/ContentBuilder.kt b/core/src/main/kotlin/Kotlin/ContentBuilder.kt index c60625a4..573b41b6 100644 --- a/core/src/main/kotlin/Kotlin/ContentBuilder.kt +++ b/core/src/main/kotlin/Kotlin/ContentBuilder.kt @@ -168,7 +168,7 @@ fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: LinkR } } -private fun MarkdownNode.getLabelText() = children.filter { it.type == MarkdownTokenTypes.TEXT || it.type == MarkdownTokenTypes.EMPH }.joinToString("") { it.text } +private fun MarkdownNode.getLabelText() = children.filter { it.type == MarkdownTokenTypes.TEXT || it.type == MarkdownTokenTypes.EMPH || it.type == MarkdownTokenTypes.COLON }.joinToString("") { it.text } private fun keepEol(node: ContentNode) = node is ContentParagraph || node is ContentSection || node is ContentBlockCode private fun processingList(node: ContentNode) = node is ContentOrderedList || node is ContentUnorderedList diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt index d73bef4a..88494581 100644 --- a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt +++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt @@ -10,7 +10,7 @@ class DeclarationLinkResolver @Inject constructor(val resolutionFacade: DokkaResolutionFacade, val refGraph: NodeReferenceGraph, val logger: DokkaLogger, - val options: DocumentationOptions, + val passConfiguration: DokkaConfiguration.PassConfiguration, val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver, val elementSignatureProvider: ElementSignatureProvider) { @@ -34,14 +34,15 @@ class DeclarationLinkResolver val signature = elementSignatureProvider.signature(symbol) val referencedAt = fromDescriptor.signatureWithSourceLocation() - return ContentNodeLazyLink(href, { -> + 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. " + + "This is probably caused by invalid configuration of cross-module dependencies") } target - }) + } } if ("/" in href) { return ContentExternalLink(href) @@ -63,7 +64,7 @@ class DeclarationLinkResolver if (symbol is CallableMemberDescriptor && symbol.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { return symbol.overriddenDescriptors.firstOrNull() } - if (symbol is TypeAliasDescriptor && !symbol.isDocumented(options)) { + if (symbol is TypeAliasDescriptor && !symbol.isDocumented(passConfiguration)) { return symbol.classDescriptor } return symbol diff --git a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt index d1f98184..ce20aeec 100644 --- a/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt +++ b/core/src/main/kotlin/Kotlin/DescriptorDocumentationParser.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.resolve.source.PsiSourceElement class DescriptorDocumentationParser - @Inject constructor(val options: DocumentationOptions, + @Inject constructor(val options: DokkaConfiguration.PassConfiguration, val logger: DokkaLogger, val linkResolver: DeclarationLinkResolver, val resolutionFacade: DokkaResolutionFacade, @@ -74,7 +74,7 @@ class DescriptorDocumentationParser } val tree = parseMarkdown(kdocText) val linkMap = LinkMap.buildLinkMap(tree.node, kdocText) - val content = buildContent(tree, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }), inline) + val content = buildContent(tree, LinkResolver(linkMap) { href -> linkResolver.resolveContentLink(contextDescriptor, href) }, inline) if (kdoc is KDocSection) { val tags = kdoc.getTags() tags.forEach { @@ -87,7 +87,7 @@ class DescriptorDocumentationParser val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName()) val sectionContent = it.getContent() val markdownNode = parseMarkdown(sectionContent) - buildInlineContentTo(markdownNode, section, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) })) + buildInlineContentTo(markdownNode, section, LinkResolver(linkMap) { href -> linkResolver.resolveContentLink(contextDescriptor, href) }) } } } @@ -129,7 +129,7 @@ class DescriptorDocumentationParser FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.FROM_IDE) anyClassDescriptors.forEach { val anyMethod = (it as ClassDescriptor).getMemberScope(listOf()) - .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS, { it == descriptor.name }) + .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS) { it == descriptor.name } .single() val kdoc = anyMethod.findKDoc() if (kdoc != null) { diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt index 38804e39..eb0399c7 100644 --- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt +++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt @@ -3,9 +3,10 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiJavaFile -import org.jetbrains.dokka.DokkaConfiguration.* +import org.jetbrains.dokka.DokkaConfiguration.PassConfiguration import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.coroutines.hasFunctionOrSuspendFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor @@ -32,64 +33,13 @@ 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.immediateSupertypes +import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny +import org.jetbrains.kotlin.types.typeUtil.isTypeParameter 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 import com.google.inject.name.Named as GuiceNamed -class DocumentationOptions(val outputDir: String, - val outputFormat: String, - includeNonPublic: Boolean = false, - val includeRootPackage: Boolean = false, - reportUndocumented: Boolean = true, - val skipEmptyPackages: Boolean = true, - skipDeprecated: Boolean = false, - jdkVersion: Int = 6, - val generateIndexPages: Boolean = true, - val sourceLinks: List<SourceLinkDefinition> = emptyList(), - val impliedPlatforms: List<String> = emptyList(), - // Sorted by pattern length - perPackageOptions: List<PackageOptions> = emptyList(), - externalDocumentationLinks: List<ExternalDocumentationLink> = emptyList(), - noStdlibLink: Boolean, - noJdkLink: Boolean = false, - val languageVersion: String?, - val apiVersion: String?, - cacheRoot: String? = null, - 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") - } - - val perPackageOptions = perPackageOptions.sortedByDescending { it.prefix.length } - val rootPackageOptions = PackageOptionsImpl("", includeNonPublic, reportUndocumented, skipDeprecated) - - fun effectivePackageOptions(pack: String): PackageOptions = perPackageOptions.firstOrNull { pack == it.prefix || pack.startsWith(it.prefix + ".") } ?: rootPackageOptions - fun effectivePackageOptions(pack: FqName): PackageOptions = effectivePackageOptions(pack.asString()) - - val defaultLinks = run { - val links = mutableListOf<ExternalDocumentationLink>() - 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 - } - - val externalDocumentationLinks = defaultLinks + externalDocumentationLinks - - val cacheRoot: Path? = when { - cacheRoot == "default" -> Paths.get(System.getProperty("user.home"), ".cache", "dokka") - cacheRoot != null -> Paths.get(cacheRoot) - else -> null - } -} - private fun isExtensionForExternalClass(extensionFunctionDescriptor: DeclarationDescriptor, extensionReceiverDescriptor: DeclarationDescriptor, allFqNames: Collection<FqName>): Boolean { @@ -119,7 +69,7 @@ val ignoredSupertypes = setOf( class DocumentationBuilder @Inject constructor(val resolutionFacade: DokkaResolutionFacade, val descriptorDocumentationParser: DescriptorDocumentationParser, - val options: DocumentationOptions, + val passConfiguration: DokkaConfiguration.PassConfiguration, val refGraph: NodeReferenceGraph, val platformNodeRegistry: PlatformNodeRegistry, val logger: DokkaLogger, @@ -131,7 +81,7 @@ class DocumentationBuilder val knownModifiers = setOf( KtTokens.PUBLIC_KEYWORD, KtTokens.PROTECTED_KEYWORD, KtTokens.INTERNAL_KEYWORD, KtTokens.PRIVATE_KEYWORD, KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD, - KtTokens.OVERRIDE_KEYWORD) + KtTokens.OVERRIDE_KEYWORD, KtTokens.INLINE_KEYWORD) fun link(node: DocumentationNode, descriptor: DeclarationDescriptor, kind: RefKind) { refGraph.link(node, descriptor.signature(), kind) @@ -189,6 +139,13 @@ class DocumentationBuilder appendTextNode(modifier, NodeKind.Modifier) } + fun DocumentationNode.appendInline(descriptor: DeclarationDescriptor, psi: KtModifierListOwner) { + if (!psi.hasModifier(KtTokens.INLINE_KEYWORD)) return + if (descriptor is FunctionDescriptor + && descriptor.valueParameters.none { it.hasFunctionOrSuspendFunctionType }) return + appendTextNode(KtTokens.INLINE_KEYWORD.value, NodeKind.Modifier) + } + fun DocumentationNode.appendVisibility(descriptor: DeclarationDescriptorWithVisibility) { val modifier = descriptor.visibility.normalize().displayName appendTextNode(modifier, NodeKind.Modifier) @@ -328,7 +285,13 @@ class DocumentationBuilder .detail(NodeKind.Value) .name.removeSurrounding("\"") - append(platformNodeRegistry["Kotlin " + kotlinVersion], RefKind.Platform) + sinceKotlin = kotlinVersion + } + + fun DocumentationNode.appendDefaultSinceKotlin() { + if (sinceKotlin == null) { + sinceKotlin = passConfiguration.sinceKotlin + } } fun DocumentationNode.appendModifiers(descriptor: DeclarationDescriptor) { @@ -342,6 +305,7 @@ class DocumentationBuilder appendTextNode(it.value, NodeKind.Modifier) } } + appendInline(descriptor, psi) } fun DocumentationNode.appendDefaultPlatforms(descriptor: DeclarationDescriptor) { @@ -355,7 +319,7 @@ class DocumentationBuilder fun DocumentationNode.isSinceKotlin() = name == "SinceKotlin" && kind == NodeKind.Annotation fun DocumentationNode.appendSourceLink(sourceElement: SourceElement) { - appendSourceLink(sourceElement.getPsi(), options.sourceLinks) + appendSourceLink(sourceElement.getPsi(), passConfiguration.sourceLinks) } fun DocumentationNode.appendSignature(descriptor: DeclarationDescriptor) { @@ -363,7 +327,7 @@ class DocumentationBuilder } fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: RefKind): DocumentationNode? { - if (!descriptor.isGenerated() && descriptor.isDocumented(options)) { + if (!descriptor.isGenerated() && descriptor.isDocumented(passConfiguration)) { val node = descriptor.build() append(node, kind) return node @@ -390,7 +354,7 @@ class DocumentationBuilder fun DocumentationNode.appendOrUpdateMember(descriptor: DeclarationDescriptor) { - if (descriptor.isGenerated() || !descriptor.isDocumented(options)) return + if (descriptor.isGenerated() || !descriptor.isDocumented(passConfiguration)) return val existingNode = refGraph.lookup(descriptor.signature()) if (existingNode != null) { @@ -462,10 +426,10 @@ class DocumentationBuilder val allFqNames = fragments.map { it.fqName }.distinct() for (packageName in allFqNames) { - if (packageName.isRoot && !options.includeRootPackage) continue + if (packageName.isRoot && !passConfiguration.includeRootPackage) continue val declarations = fragments.filter { it.fqName == packageName }.flatMap { it.getMemberScope().getContributedDescriptors() } - if (options.skipEmptyPackages && declarations.none { it.isDocumented(options) }) continue + if (passConfiguration.skipEmptyPackages && declarations.none { it.isDocumented(passConfiguration) }) continue logger.info(" package $packageName: ${declarations.count()} declarations") val packageNode = findOrCreatePackageNode(this, packageName.asString(), packageContent, this@DocumentationBuilder.refGraph) packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode, @@ -492,7 +456,7 @@ class DocumentationBuilder }.flatten() val allDescriptors = - if (options.collectInheritedExtensionsFromLibraries) { + if (passConfiguration.collectInheritedExtensionsFromLibraries) { allPackageViewDescriptors.map { it.memberScope } } else { fragments.asSequence().map { it.getMemberScope() } @@ -514,13 +478,20 @@ class DocumentationBuilder .filter { it.extensionReceiverParameter != null } val extensionFunctionsByName = allExtensionFunctions.groupBy { it.name } + fun isIgnoredReceiverType(type: KotlinType) = + type.isDynamic() || + type.isAnyOrNullableAny() || + (type.isTypeParameter() && type.immediateSupertypes().all { it.isAnyOrNullableAny() }) + + for (extensionFunction in allExtensionFunctions) { + val extensionReceiverParameter = extensionFunction.extensionReceiverParameter!! if (extensionFunction.dispatchReceiverParameter != null) continue val possiblyShadowingFunctions = extensionFunctionsByName[extensionFunction.name] ?.filter { fn -> fn.canShadow(extensionFunction) } ?: emptyList() - if (extensionFunction.extensionReceiverParameter?.type?.isDynamic() == true) continue + if (isIgnoredReceiverType(extensionReceiverParameter.type)) continue val subclasses = classHierarchy.filter { (key) -> key.isExtensionApplicable(extensionFunction) } if (subclasses.isEmpty()) continue @@ -628,6 +599,7 @@ class DocumentationBuilder val node = nodeForDescriptor(this, NodeKind.TypeAlias) if (!external) { + node.appendDefaultSinceKotlin() node.appendAnnotations(this) } node.appendModifiers(this) @@ -665,6 +637,7 @@ class DocumentationBuilder for ((descriptor, inheritedLinkKind, extraModifier) in collectMembersToDocument()) { node.appendClassMember(descriptor, inheritedLinkKind, extraModifier) } + node.appendDefaultSinceKotlin() node.appendAnnotations(this) } node.appendModifiers(this) @@ -697,7 +670,7 @@ class DocumentationBuilder .mapTo(result) { ClassMember(it, extraModifier = "static") } val companionObjectDescriptor = companionObjectDescriptor - if (companionObjectDescriptor != null && companionObjectDescriptor.isDocumented(options)) { + if (companionObjectDescriptor != null && companionObjectDescriptor.isDocumented(passConfiguration)) { val descriptors = companionObjectDescriptor.defaultType.memberScope.getContributedDescriptors() val descriptorsToDocument = descriptors.filter { it !is CallableDescriptor || !it.isInheritedFromAny() } descriptorsToDocument.mapTo(result) { @@ -725,6 +698,7 @@ class DocumentationBuilder val node = nodeForDescriptor(this, NodeKind.Constructor) node.appendInPageChildren(valueParameters, RefKind.Detail) node.appendDefaultPlatforms(this) + node.appendDefaultSinceKotlin() register(this, node) return node } @@ -749,6 +723,9 @@ class DocumentationBuilder extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) } node.appendInPageChildren(valueParameters, RefKind.Detail) node.appendType(returnType) + if (!external) { + node.appendDefaultSinceKotlin() + } node.appendAnnotations(this) node.appendModifiers(this) if (!external) { @@ -786,6 +763,9 @@ class DocumentationBuilder node.appendInPageChildren(typeParameters, RefKind.Detail) extensionReceiverParameter?.let { node.appendChild(it, RefKind.Detail) } node.appendType(returnType) + if (!external) { + node.appendDefaultSinceKotlin() + } node.appendAnnotations(this) node.appendModifiers(this) if (!external) { @@ -849,6 +829,7 @@ class DocumentationBuilder } } } + node.appendDefaultSinceKotlin() node.appendAnnotations(this) node.appendModifiers(this) if (varargElementType != null && node.details(NodeKind.Modifier).none { it.name == "vararg" }) { @@ -939,23 +920,27 @@ class DocumentationBuilder } - fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor, - externalClassNodes: MutableMap<FqName, DocumentationNode>, - allFqNames: Collection<FqName>): DocumentationNode { + fun DocumentationNode.getParentForPackageMember( + descriptor: DeclarationDescriptor, + externalClassNodes: MutableMap<FqName, DocumentationNode>, + allFqNames: Collection<FqName>, + packageName: FqName + ): DocumentationNode { if (descriptor is CallableMemberDescriptor) { val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() if (extensionClassDescriptor != null && isExtensionForExternalClass(descriptor, extensionClassDescriptor, allFqNames) && !ErrorUtils.isError(extensionClassDescriptor)) { val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor) - return externalClassNodes.getOrPut(fqName, { + return externalClassNodes.getOrPut(fqName) { val newNode = DocumentationNode(fqName.asString(), Content.Empty, NodeKind.ExternalClass) val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(extensionClassDescriptor) if (externalLink != null) { newNode.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link) } append(newNode, RefKind.Member) + refGraph.register("${packageName.asString()}:${extensionClassDescriptor.signature()}", newNode) newNode - }) + } } } return this @@ -963,12 +948,12 @@ class DocumentationBuilder } -fun DeclarationDescriptor.isDocumented(options: DocumentationOptions): Boolean { - return (options.effectivePackageOptions(fqNameSafe).includeNonPublic +fun DeclarationDescriptor.isDocumented(passConfiguration: DokkaConfiguration.PassConfiguration): Boolean { + return (passConfiguration.effectivePackageOptions(fqNameSafe).includeNonPublic || this !is MemberDescriptor || this.visibility.isPublicAPI) - && !isDocumentationSuppressed(options) - && (!options.effectivePackageOptions(fqNameSafe).skipDeprecated || !isDeprecated()) + && !isDocumentationSuppressed(passConfiguration) + && (!passConfiguration.effectivePackageOptions(fqNameSafe).skipDeprecated || !isDeprecated()) } private fun DeclarationDescriptor.isGenerated() = this is CallableMemberDescriptor && kind != CallableMemberDescriptor.Kind.DECLARATION @@ -982,8 +967,13 @@ class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder { val externalClassNodes = hashMapOf<FqName, DocumentationNode>() declarations.forEach { descriptor -> with(documentationBuilder) { - if (descriptor.isDocumented(options)) { - val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes, allFqNames) + if (descriptor.isDocumented(passConfiguration)) { + val parent = packageNode.getParentForPackageMember( + descriptor, + externalClassNodes, + allFqNames, + packageName + ) parent.appendOrUpdateMember(descriptor) } } @@ -994,14 +984,14 @@ class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder { class KotlinJavaDocumentationBuilder @Inject constructor(val resolutionFacade: DokkaResolutionFacade, val documentationBuilder: DocumentationBuilder, - val options: DocumentationOptions, + val passConfiguration: DokkaConfiguration.PassConfiguration, val logger: DokkaLogger) : JavaDocumentationBuilder { override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) { val classDescriptors = file.classes.map { it.getJavaClassDescriptor(resolutionFacade) } - if (classDescriptors.any { it != null && it.isDocumented(options) }) { + if (classDescriptors.any { it != null && it.isDocumented(passConfiguration) }) { val packageNode = findOrCreatePackageNode(module, file.packageName, packageContent, documentationBuilder.refGraph) for (descriptor in classDescriptors.filterNotNull()) { @@ -1031,13 +1021,13 @@ fun AnnotationDescriptor.mustBeDocumented(): Boolean { return annotationClass.isDocumentedAnnotation() } -fun DeclarationDescriptor.isDocumentationSuppressed(options: DocumentationOptions): Boolean { +fun DeclarationDescriptor.isDocumentationSuppressed(passConfiguration: DokkaConfiguration.PassConfiguration): Boolean { - if (options.effectivePackageOptions(fqNameSafe).suppress) return true + if (passConfiguration.effectivePackageOptions(fqNameSafe).suppress) return true val path = this.findPsi()?.containingFile?.virtualFile?.path if (path != null) { - if (File(path).absoluteFile in options.suppressedFiles) return true + if (path in passConfiguration.suppressedFiles) return true } val doc = findKDoc() @@ -1136,8 +1126,8 @@ fun DeclarationDescriptor.sourceLocation(): String? { return null } -fun DocumentationModule.prepareForGeneration(options: DocumentationOptions) { - if (options.generateIndexPages) { +fun DocumentationModule.prepareForGeneration(configuration: DokkaConfiguration) { + if (configuration.generateIndexPages) { generateAllTypesNode() } nodeRefGraph.resolveReferences() @@ -1145,7 +1135,9 @@ 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 } } + .flatMap { it.members.filter { + it.kind in NodeKind.classLike || it.kind == NodeKind.ExternalClass + || (it.kind == NodeKind.GroupNode && it.origins.all { it.kind in NodeKind.classLike }) } } .sortedBy { if (it.kind == NodeKind.ExternalClass) it.name.substringAfterLast('.').toLowerCase() else it.name.toLowerCase() } val allTypesNode = DocumentationNode("alltypes", Content.Empty, NodeKind.AllTypes) @@ -1161,4 +1153,12 @@ fun ClassDescriptor.supertypesWithAnyPrecise(): Collection<KotlinType> { return emptyList() } return typeConstructor.supertypesWithAny() -}
\ No newline at end of file +} + +fun PassConfiguration.effectivePackageOptions(pack: String): DokkaConfiguration.PackageOptions { + val rootPackageOptions = PackageOptionsImpl("", includeNonPublic, reportUndocumented, skipDeprecated, false) + return perPackageOptions.firstOrNull { pack == it.prefix || pack.startsWith(it.prefix + ".") } ?: rootPackageOptions +} + +fun PassConfiguration.effectivePackageOptions(pack: FqName): DokkaConfiguration.PackageOptions = effectivePackageOptions(pack.asString()) + diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt index a8129793..793f9589 100644 --- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt +++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt @@ -22,29 +22,48 @@ import java.net.HttpURLConnection import java.net.URL import java.net.URLConnection import java.nio.file.Path +import java.nio.file.Paths import java.security.MessageDigest import javax.inject.Named import kotlin.reflect.full.findAnnotation fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) } +typealias PackageFqNameToLocation = MutableMap<FqName, PackageListProvider.ExternalDocumentationRoot> + @Singleton -class ExternalDocumentationLinkResolver @Inject constructor( - val options: DocumentationOptions, - @Named("libraryResolutionFacade") val libraryResolutionFacade: DokkaResolutionFacade, - val logger: DokkaLogger +class PackageListProvider @Inject constructor( + val configuration: DokkaConfiguration, + val logger: DokkaLogger ) { + val storage = mutableMapOf<DokkaConfiguration.ExternalDocumentationLink, PackageFqNameToLocation>() - val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>() - val formats = mutableMapOf<String, InboundExternalLinkResolutionService>() + val cacheDir: Path? = when { + configuration.cacheRoot == "default" -> Paths.get(System.getProperty("user.home"), ".cache", "dokka") + configuration.cacheRoot != null -> Paths.get(configuration.cacheRoot) + else -> null + }?.resolve("packageListCache")?.apply { createDirectories() } - class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>) { - override fun toString(): String = rootUrl.toString() + val cachedProtocols = setOf("http", "https", "ftp") + + init { + for (conf in configuration.passesConfigurations) { + for (link in conf.externalDocumentationLinks) { + if (link in storage) { + continue + } + + try { + loadPackageList(link) + } catch (e: Exception) { + throw RuntimeException("Exception while loading package-list from $link", e) + } + } + + } } - val cacheDir: Path? = options.cacheRoot?.resolve("packageListCache")?.apply { createDirectories() } - val cachedProtocols = setOf("http", "https", "ftp") fun URL.doOpenConnectionToReadContent(timeout: Int = 10000, redirectsAllowed: Int = 16): URLConnection { val connection = this.openConnection() @@ -120,23 +139,23 @@ class ExternalDocumentationLinkResolver @Inject constructor( val (params, packages) = packageListStream - .bufferedReader() - .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } } + .bufferedReader() + .useLines { lines -> lines.partition { it.startsWith(ExternalDocumentationLinkResolver.DOKKA_PARAM_PREFIX) } } val paramsMap = params.asSequence() - .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) } - .groupBy({ (key, _) -> key }, { (_, value) -> value }) + .map { it.removePrefix(ExternalDocumentationLinkResolver.DOKKA_PARAM_PREFIX).split(":", limit = 2) } + .groupBy({ (key, _) -> key }, { (_, value) -> value }) val format = paramsMap["format"]?.singleOrNull() ?: "javadoc" val locations = paramsMap["location"].orEmpty() - .map { it.split("\u001f", limit = 2) } - .map { (key, value) -> key to value } - .toMap() + .map { it.split("\u001f", limit = 2) } + .map { (key, value) -> key to value } + .toMap() - val defaultResolverDesc = services["dokka-default"]!! - val resolverDesc = services[format] + val defaultResolverDesc = ExternalDocumentationLinkResolver.services.getValue("dokka-default") + val resolverDesc = ExternalDocumentationLinkResolver.services[format] ?: defaultResolverDesc.takeIf { format in formatsWithDefaultResolver } ?: defaultResolverDesc.also { logger.warn("Couldn't find InboundExternalLinkResolutionService(format = `$format`) for $link, using Dokka default") @@ -153,16 +172,52 @@ class ExternalDocumentationLinkResolver @Inject constructor( val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations) - packages.map { FqName(it) }.forEach { packageFqNameToLocation[it] = rootInfo } + val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>() + storage[link] = packageFqNameToLocation + + val fqNames = packages.map { FqName(it) } + for(name in fqNames) { + packageFqNameToLocation[name] = rootInfo + } + } + + + + class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>) { + override fun toString(): String = rootUrl.toString() + } + + companion object { + private val formatsWithDefaultResolver = + ServiceLocator + .allServices("format") + .filter { + val desc = ServiceLocator.lookup<FormatDescriptor>(it) as? FileGeneratorBasedFormatDescriptor + desc?.generatorServiceClass == FileGenerator::class + }.map { it.name } + .toSet() + } +} + +class ExternalDocumentationLinkResolver @Inject constructor( + val configuration: DokkaConfiguration, + val passConfiguration: DokkaConfiguration.PassConfiguration, + @Named("libraryResolutionFacade") val libraryResolutionFacade: DokkaResolutionFacade, + val logger: DokkaLogger, + val packageListProvider: PackageListProvider +) { + + val formats = mutableMapOf<String, InboundExternalLinkResolutionService>() + val packageFqNameToLocation = mutableMapOf<FqName, PackageListProvider.ExternalDocumentationRoot>() + init { - options.externalDocumentationLinks.forEach { - try { - loadPackageList(it) - } catch (e: Exception) { - throw RuntimeException("Exception while loading package-list from $it", e) - } + val fqNameToLocationMaps = passConfiguration.externalDocumentationLinks + .mapNotNull { packageListProvider.storage[it] } + + for(map in fqNameToLocationMaps) { + packageFqNameToLocation.putAll(map) } } @@ -191,14 +246,6 @@ 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() } } diff --git a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt index 00a795d0..ee9d8c51 100644 --- a/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt +++ b/core/src/main/kotlin/Kotlin/KotlinAsJavaDocumentationBuilder.kt @@ -30,7 +30,7 @@ class KotlinAsJavaDocumentationBuilder return } - val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options, + val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.passConfiguration, documentationBuilder.refGraph, kotlinAsJavaDocumentationParser) @@ -42,8 +42,7 @@ class KotlinAsJavaDocumentationBuilder fun PsiClass.isVisibleInDocumentation(): Boolean { val origin: KtDeclaration = (this as KtLightElement<*, *>).kotlinOrigin as? KtDeclaration ?: return true - return origin.hasModifier(KtTokens.INTERNAL_KEYWORD) != true && - origin.hasModifier(KtTokens.PRIVATE_KEYWORD) != true + return !origin.hasModifier(KtTokens.INTERNAL_KEYWORD) && !origin.hasModifier(KtTokens.PRIVATE_KEYWORD) } } diff --git a/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt index bcac0182..c7187b23 100644 --- a/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt +++ b/core/src/main/kotlin/Kotlin/KotlinElementSignatureProvider.kt @@ -3,6 +3,7 @@ package org.jetbrains.dokka import com.intellij.psi.PsiElement import com.intellij.psi.PsiMember import com.intellij.psi.PsiPackage +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.name.FqName @@ -22,13 +23,11 @@ class KotlinElementSignatureProvider @Inject constructor( } -fun PsiElement.extractDescriptor(resolutionFacade: DokkaResolutionFacade): DeclarationDescriptor? { - val forPsi = this - - return when (forPsi) { - is KtLightElement<*, *> -> return (forPsi.kotlinOrigin!!).extractDescriptor(resolutionFacade) +fun PsiElement.extractDescriptor(resolutionFacade: DokkaResolutionFacade): DeclarationDescriptor? = + when (val forPsi = this) { + is KtLightClassForFacade -> resolutionFacade.moduleDescriptor.getPackage(forPsi.fqName) + is KtLightElement<*, *> -> (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 5f43c22e..7310610f 100644 --- a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt +++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt @@ -62,7 +62,7 @@ class KotlinLanguageService : CommonLanguageService() { } private fun List<DocumentationNode>.getReceiverKind(): ReceiverKind? { - val qNames = map { it.getReceiverQName() }.filterNotNull() + val qNames = mapNotNull { it.getReceiverQName() } if (qNames.size != size) return null @@ -208,7 +208,7 @@ class KotlinLanguageService : CommonLanguageService() { nowrap: Boolean ) { when (node.name) { - "final", "public", "var" -> { + "final", "public", "var", "expect", "actual", "external" -> { } else -> { if (showModifierInSummary(node) || renderMode == RenderMode.FULL) { diff --git a/core/src/main/kotlin/Languages/JavaLanguageService.kt b/core/src/main/kotlin/Languages/JavaLanguageService.kt index 59bedd02..ad66123b 100644 --- a/core/src/main/kotlin/Languages/JavaLanguageService.kt +++ b/core/src/main/kotlin/Languages/JavaLanguageService.kt @@ -84,7 +84,7 @@ class JavaLanguageService : LanguageService { return if (constraints.none()) node.name else { - node.name + " extends " + constraints.map { renderType(node) }.joinToString() + node.name + " extends " + constraints.joinToString { renderType(node) } } } @@ -97,7 +97,7 @@ class JavaLanguageService : LanguageService { val typeParameters = node.details(NodeKind.TypeParameter) if (typeParameters.any()) { append("<") - append(typeParameters.map { renderTypeParameter(it) }.joinToString()) + append(typeParameters.joinToString { renderTypeParameter(it) }) append("> ") } }.toString() @@ -142,9 +142,9 @@ class JavaLanguageService : LanguageService { val receiver = node.details(NodeKind.Receiver).singleOrNull() append("(") if (receiver != null) - (listOf(receiver) + node.details(NodeKind.Parameter)).map { renderParameter(it) }.joinTo(this) + (listOf(receiver) + node.details(NodeKind.Parameter)).joinTo(this) { renderParameter(it) } else - node.details(NodeKind.Parameter).map { renderParameter(it) }.joinTo(this) + node.details(NodeKind.Parameter).joinTo(this) { renderParameter(it) } append(")") }.toString() diff --git a/core/src/main/kotlin/Locations/Location.kt b/core/src/main/kotlin/Locations/Location.kt index 4cb0ac39..63c9b913 100644 --- a/core/src/main/kotlin/Locations/Location.kt +++ b/core/src/main/kotlin/Locations/Location.kt @@ -26,7 +26,7 @@ data class FileLocation(val file: File) : Location { } val ownerFolder = file.parentFile!! val relativePath = ownerFolder.toPath().relativize(other.file.toPath()).toString().replace(File.separatorChar, '/') - return if (anchor == null) relativePath else relativePath + "#" + anchor + return if (anchor == null) relativePath else "$relativePath#$anchor" } } @@ -40,10 +40,25 @@ fun relativePathToNode(qualifiedName: List<String>, hasMembers: Boolean): String } } +fun nodeActualQualifier(node: DocumentationNode): List<DocumentationNode> { + val topLevelPage = node.references(RefKind.TopLevelPage).singleOrNull()?.to + if (topLevelPage != null) { + return nodeActualQualifier(topLevelPage) + } + return node.owner?.let { nodeActualQualifier(it) }.orEmpty() + node +} + +fun relativePathToNode(node: DocumentationNode): String { + val qualifier = nodeActualQualifier(node) + return relativePathToNode( + qualifier.map { it.name }, + qualifier.last().members.any() + ) +} -fun relativePathToNode(node: DocumentationNode) = relativePathToNode(node.path.map { it.name }, node.members.any()) fun identifierToFilename(path: String): String { + if (path.isEmpty()) return "--root--" val escaped = path.replace('<', '-').replace('>', '-') val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() } return if (lowercase == "index") "--index--" else lowercase diff --git a/core/src/main/kotlin/Model/Content.kt b/core/src/main/kotlin/Model/Content.kt index c142f4a4..5530e1b4 100644 --- a/core/src/main/kotlin/Model/Content.kt +++ b/core/src/main/kotlin/Model/Content.kt @@ -171,10 +171,10 @@ class ContentSection(val tag: String, val subjectName: String?) : ContentBlock() } object ContentTags { - val Description = "Description" - val SeeAlso = "See Also" - val Return = "Return" - val Exceptions = "Exceptions" + const val Description = "Description" + const val SeeAlso = "See Also" + const val Return = "Return" + const val Exceptions = "Exceptions" } fun content(body: ContentBlock.() -> Unit): ContentBlock { @@ -227,7 +227,11 @@ open class Content(): ContentBlock() { sections.firstOrNull { tag.equals(it.tag, ignoreCase = true) } companion object { - val Empty = Content() + val Empty = object: Content() { + override fun toString(): String { + return "EMPTY_CONTENT" + } + } fun of(vararg child: ContentNode): Content { val result = MutableContent() diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt index ad7801f2..311b46e4 100644 --- a/core/src/main/kotlin/Model/DocumentationNode.kt +++ b/core/src/main/kotlin/Model/DocumentationNode.kt @@ -76,7 +76,14 @@ open class DocumentationNode(val name: String, var content: Content = content private set - val summary: ContentNode get() = content.summary + val summary: ContentNode get() = when (kind) { + NodeKind.GroupNode -> this.origins + .map { it.content } + .firstOrNull { !it.isEmpty() } + ?.summary ?: ContentEmpty + else -> content.summary + } + val owner: DocumentationNode? get() = references(RefKind.Owner).singleOrNull()?.to @@ -84,6 +91,9 @@ open class DocumentationNode(val name: String, get() = references(RefKind.Detail).map { it.to } val members: List<DocumentationNode> get() = references(RefKind.Member).map { it.to } + val origins: List<DocumentationNode> + get() = references(RefKind.Origin).map { it.to } + val inheritedMembers: List<DocumentationNode> get() = references(RefKind.InheritedMember).map { it.to } val allInheritedMembers: List<DocumentationNode> @@ -109,6 +119,15 @@ open class DocumentationNode(val name: String, val externalType: DocumentationNode? get() = references(RefKind.ExternalType).map { it.to }.firstOrNull() + var sinceKotlin: String? + get() = references(RefKind.SinceKotlin).singleOrNull()?.to?.name + set(value) { + dropReferences { it.kind == RefKind.SinceKotlin } + if (value != null) { + append(DocumentationNode(value, Content.Empty, NodeKind.Value), RefKind.SinceKotlin) + } + } + val supertypes: List<DocumentationNode> get() = details(NodeKind.Supertype) @@ -134,6 +153,10 @@ open class DocumentationNode(val name: String, references.add(DocumentationReference(this, to, kind)) } + fun addReference(reference: DocumentationReference) { + references.add(reference) + } + fun dropReferences(predicate: (DocumentationReference) -> Boolean) { references.removeAll(predicate) } @@ -154,10 +177,11 @@ open class DocumentationNode(val name: String, fun inheritedCompanionObjectMembers(kind: NodeKind): List<DocumentationNode> = inheritedCompanionObjectMembers.filter { it.kind == kind } fun links(kind: NodeKind): List<DocumentationNode> = links.filter { it.kind == kind } - fun detail(kind: NodeKind): DocumentationNode = details.filter { it.kind == kind }.single() - fun detailOrNull(kind: NodeKind): DocumentationNode? = details.filter { it.kind == kind }.singleOrNull() - fun member(kind: NodeKind): DocumentationNode = members.filter { it.kind == kind }.single() - fun link(kind: NodeKind): DocumentationNode = links.filter { it.kind == kind }.single() + fun detail(kind: NodeKind): DocumentationNode = details.single { it.kind == kind } + fun detailOrNull(kind: NodeKind): DocumentationNode? = details.singleOrNull { it.kind == kind } + fun member(kind: NodeKind): DocumentationNode = members.single { it.kind == kind } + fun link(kind: NodeKind): DocumentationNode = links.single { it.kind == kind } + fun references(kind: RefKind): List<DocumentationReference> = references.filter { it.kind == kind } fun allReferences(): Set<DocumentationReference> = references @@ -167,9 +191,9 @@ open class DocumentationNode(val name: String, } } -class DocumentationModule(name: String, content: Content = Content.Empty) +class DocumentationModule(name: String, content: Content = Content.Empty, val nodeRefGraph: NodeReferenceGraph = NodeReferenceGraph()) : DocumentationNode(name, content, NodeKind.Module) { - val nodeRefGraph = NodeReferenceGraph() + } val DocumentationNode.path: List<DocumentationNode> @@ -201,6 +225,7 @@ 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) + RefKind.Origin -> child.addReferenceTo(this, RefKind.Owner) else -> { /* Do not add any links back for other types */ } } diff --git a/core/src/main/kotlin/Model/DocumentationReference.kt b/core/src/main/kotlin/Model/DocumentationReference.kt index 89ec1b3e..0b890a78 100644 --- a/core/src/main/kotlin/Model/DocumentationReference.kt +++ b/core/src/main/kotlin/Model/DocumentationReference.kt @@ -19,42 +19,83 @@ enum class RefKind { Deprecation, TopLevelPage, Platform, - ExternalType + ExternalType, + Origin, + SinceKotlin } data class DocumentationReference(val from: DocumentationNode, val to: DocumentationNode, val kind: RefKind) { } -class PendingDocumentationReference(val lazyNodeFrom: () -> DocumentationNode?, - val lazyNodeTo: () -> DocumentationNode?, +sealed class NodeResolver { + abstract fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode? + class BySignature(var signature: String) : NodeResolver() { + override fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode? { + return nodeRephGraph.lookup(signature) + } + } + + class Exact(var exactNode: DocumentationNode) : NodeResolver() { + override fun resolve(nodeRephGraph: NodeReferenceGraph): DocumentationNode? { + return exactNode + } + } +} + +class PendingDocumentationReference(val lazyNodeFrom: NodeResolver, + val lazyNodeTo: NodeResolver, val kind: RefKind) { - fun resolve() { - val fromNode = lazyNodeFrom() - val toNode = lazyNodeTo() + fun resolve(nodeRephGraph: NodeReferenceGraph) { + val fromNode = lazyNodeFrom.resolve(nodeRephGraph) + val toNode = lazyNodeTo.resolve(nodeRephGraph) if (fromNode != null && toNode != null) { fromNode.addReferenceTo(toNode, kind) } } } -class NodeReferenceGraph() { +class NodeReferenceGraph { private val nodeMap = hashMapOf<String, DocumentationNode>() + val nodeMapView: Map<String, DocumentationNode> + get() = HashMap(nodeMap) + val references = arrayListOf<PendingDocumentationReference>() fun register(signature: String, node: DocumentationNode) { - nodeMap.put(signature, node) + nodeMap[signature] = node } fun link(fromNode: DocumentationNode, toSignature: String, kind: RefKind) { - references.add(PendingDocumentationReference({ -> fromNode}, { -> nodeMap[toSignature]}, kind)) + references.add( + PendingDocumentationReference( + NodeResolver.Exact(fromNode), + NodeResolver.BySignature(toSignature), + kind + )) } fun link(fromSignature: String, toNode: DocumentationNode, kind: RefKind) { - references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> toNode}, kind)) + references.add( + PendingDocumentationReference( + NodeResolver.BySignature(fromSignature), + NodeResolver.Exact(toNode), + kind + ) + ) } fun link(fromSignature: String, toSignature: String, kind: RefKind) { - references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> nodeMap[toSignature]}, kind)) + references.add( + PendingDocumentationReference( + NodeResolver.BySignature(fromSignature), + NodeResolver.BySignature(toSignature), + kind + ) + ) + } + + fun addReference(reference: PendingDocumentationReference) { + references.add(reference) } fun lookup(signature: String) = nodeMap[signature] @@ -62,13 +103,14 @@ 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`." + + "This is probably caused by invalid configuration of cross-module dependencies") } return result } fun resolveReferences() { - references.forEach { it.resolve() } + references.forEach { it.resolve(this) } } } diff --git a/core/src/main/kotlin/Model/PackageDocs.kt b/core/src/main/kotlin/Model/PackageDocs.kt index 9804f68b..b24efc5d 100644 --- a/core/src/main/kotlin/Model/PackageDocs.kt +++ b/core/src/main/kotlin/Model/PackageDocs.kt @@ -42,7 +42,7 @@ class PackageDocs targetContent = findTargetContent(headingText.trimStart()) } } else { - buildContentTo(it, targetContent, LinkResolver(linkMap, { resolveContentLink(fileName, it, linkResolveContext) })) + buildContentTo(it, targetContent, LinkResolver(linkMap) { resolveContentLink(fileName, it, linkResolveContext) }) } } } else { diff --git a/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt b/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt index 116a5c02..da74495f 100644 --- a/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt +++ b/core/src/main/kotlin/Samples/DefaultSampleProcessingService.kt @@ -20,7 +20,7 @@ import org.jetbrains.kotlin.resolve.scopes.ResolutionScope open class DefaultSampleProcessingService -@Inject constructor(val options: DocumentationOptions, +@Inject constructor(val configuration: DokkaConfiguration, val logger: DokkaLogger, val resolutionFacade: DokkaResolutionFacade) : SampleProcessingService { @@ -45,7 +45,7 @@ open class DefaultSampleProcessingService val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd() val lines = text.split("\n") val indent = lines.filter(String::isNotBlank).map { it.takeWhile(Char::isWhitespace).count() }.min() ?: 0 - val finalText = lines.map { it.drop(indent) }.joinToString("\n") + val finalText = lines.joinToString("\n") { it.drop(indent) } return ContentBlockSampleCode(importsBlock = processImports(psiElement)).apply { append(ContentText(finalText)) } } @@ -81,9 +81,7 @@ open class DefaultSampleProcessingService for (part in parts) { // short name val symbolName = Name.identifier(part) - val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL, { it == symbolName }) - .filter { it.name == symbolName } - .firstOrNull() + val partSymbol = currentScope.getContributedDescriptors(DescriptorKindFilter.ALL) { it == symbolName }.firstOrNull { it.name == symbolName } if (partSymbol == null) { symbol = null diff --git a/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt b/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt index b0988c35..4525e9d9 100644 --- a/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt +++ b/core/src/main/kotlin/Samples/KotlinWebsiteSampleProcessingService.kt @@ -1,7 +1,9 @@ package org.jetbrains.dokka.Samples import com.google.inject.Inject +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.PsiTreeUtil @@ -9,19 +11,27 @@ import org.jetbrains.dokka.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.psi.psiUtil.prevLeaf +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.ImportPath +import java.io.PrintWriter +import java.io.StringWriter + open class KotlinWebsiteSampleProcessingService -@Inject constructor(options: DocumentationOptions, +@Inject constructor(dokkaConfiguration: DokkaConfiguration, logger: DokkaLogger, resolutionFacade: DokkaResolutionFacade) - : DefaultSampleProcessingService(options, logger, resolutionFacade) { + : DefaultSampleProcessingService(dokkaConfiguration, logger, resolutionFacade) { private class SampleBuilder : KtTreeVisitorVoid() { val builder = StringBuilder() val text: String get() = builder.toString() + val errors = mutableListOf<ConvertError>() + + data class ConvertError(val e: Exception, val text: String, val loc: String) + fun KtValueArgument.extractStringArgumentValue() = (getArgumentExpression() as KtStringTemplateExpression) .entries.joinToString("") { it.text } @@ -54,27 +64,42 @@ open class KotlinWebsiteSampleProcessingService } fun convertAssertFails(expression: KtCallExpression) { - val (message, funcArgument) = expression.valueArguments + val valueArguments = expression.valueArguments + + val funcArgument: KtValueArgument + val message: KtValueArgument? + + if (valueArguments.size == 1) { + message = null + funcArgument = valueArguments.first() + } else { + message = valueArguments.first() + funcArgument = valueArguments.last() + } + builder.apply { - val argument = if (funcArgument.getArgumentExpression() is KtLambdaExpression) - PsiTreeUtil.findChildOfType(funcArgument, KtBlockExpression::class.java)?.text ?: "" - else - funcArgument.text + val argument = funcArgument.extractFunctionalArgumentText() append(argument.lines().joinToString(separator = "\n") { "// $it" }) append(" // ") - append(message.extractStringArgumentValue()) + if (message != null) { + append(message.extractStringArgumentValue()) + } append(" will fail") } } + private fun KtValueArgument.extractFunctionalArgumentText(): String { + return if (getArgumentExpression() is KtLambdaExpression) + PsiTreeUtil.findChildOfType(this, KtBlockExpression::class.java)?.text ?: "" + else + text + } + fun convertAssertFailsWith(expression: KtCallExpression) { val (funcArgument) = expression.valueArguments val (exceptionType) = expression.typeArguments builder.apply { - val argument = if (funcArgument.firstChild is KtLambdaExpression) - PsiTreeUtil.findChildOfType(funcArgument, KtBlockExpression::class.java)?.text ?: "" - else - funcArgument.text + val argument = funcArgument.extractFunctionalArgumentText() append(argument.lines().joinToString(separator = "\n") { "// $it" }) append(" // will fail with ") append(exceptionType.text) @@ -92,16 +117,51 @@ open class KotlinWebsiteSampleProcessingService } } + private fun reportProblemConvertingElement(element: PsiElement, e: Exception) { + val text = element.text + val document = PsiDocumentManager.getInstance(element.project).getDocument(element.containingFile) + + val lineInfo = if (document != null) { + val lineNumber = document.getLineNumber(element.startOffset) + "$lineNumber, ${element.startOffset - document.getLineStartOffset(lineNumber)}" + } else { + "offset: ${element.startOffset}" + } + errors += ConvertError(e, text, lineInfo) + } + override fun visitElement(element: PsiElement) { if (element is LeafPsiElement) builder.append(element.text) - super.visitElement(element) + + element.acceptChildren(object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + try { + element.accept(this@SampleBuilder) + } catch (e: Exception) { + try { + reportProblemConvertingElement(element, e) + } finally { + builder.append(element.text) //recover + } + } + } + }) } + } private fun PsiElement.buildSampleText(): String { val sampleBuilder = SampleBuilder() this.accept(sampleBuilder) + + sampleBuilder.errors.forEach { + val sw = StringWriter() + val pw = PrintWriter(sw) + it.e.printStackTrace(pw) + + logger.error("${containingFile.name}: (${it.loc}): Exception thrown while converting \n```\n${it.text}\n```\n$sw") + } return sampleBuilder.text } diff --git a/core/src/main/kotlin/Utilities/DokkaModules.kt b/core/src/main/kotlin/Utilities/DokkaModules.kt index 732cbc48..919ec30f 100644 --- a/core/src/main/kotlin/Utilities/DokkaModules.kt +++ b/core/src/main/kotlin/Utilities/DokkaModules.kt @@ -13,10 +13,21 @@ import kotlin.reflect.KClass const val impliedPlatformsName = "impliedPlatforms" +class DokkaRunModule(val configuration: DokkaConfiguration) : Module { + override fun configure(binder: Binder) { + binder.bind<DokkaConfiguration>().toInstance(configuration) + binder.bind(StringListType).annotatedWith(Names.named(impliedPlatformsName)).toInstance(configuration.impliedPlatforms) + + binder.bind(File::class.java).annotatedWith(Names.named("outputDir")).toInstance(File(configuration.outputDir)) + } + +} + class DokkaAnalysisModule(val environment: AnalysisEnvironment, - val options: DocumentationOptions, + val configuration: DokkaConfiguration, val defaultPlatformsProvider: DefaultPlatformsProvider, val nodeReferenceGraph: NodeReferenceGraph, + val passConfiguration: DokkaConfiguration.PassConfiguration, val logger: DokkaLogger) : Module { override fun configure(binder: Binder) { binder.bind<DokkaLogger>().toInstance(logger) @@ -28,29 +39,25 @@ class DokkaAnalysisModule(val environment: AnalysisEnvironment, binder.bind<DokkaResolutionFacade>().toInstance(dokkaResolutionFacade) binder.bind<DokkaResolutionFacade>().annotatedWith(Names.named("libraryResolutionFacade")).toInstance(libraryResolutionFacade) - binder.bind<DocumentationOptions>().toInstance(options) + binder.bind<DokkaConfiguration.PassConfiguration>().toInstance(passConfiguration) binder.bind<DefaultPlatformsProvider>().toInstance(defaultPlatformsProvider) binder.bind<NodeReferenceGraph>().toInstance(nodeReferenceGraph) - val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", options.outputFormat) + val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", configuration.format) descriptor.configureAnalysis(binder) } } object StringListType : TypeLiteral<@JvmSuppressWildcards List<String>>() -class DokkaOutputModule(val options: DocumentationOptions, +class DokkaOutputModule(val configuration: DokkaConfiguration, val logger: DokkaLogger) : Module { override fun configure(binder: Binder) { - binder.bind(File::class.java).annotatedWith(Names.named("outputDir")).toInstance(File(options.outputDir)) - - binder.bind<DocumentationOptions>().toInstance(options) binder.bind<DokkaLogger>().toInstance(logger) - binder.bind(StringListType).annotatedWith(Names.named(impliedPlatformsName)).toInstance(options.impliedPlatforms) - val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", options.outputFormat) + val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", configuration.format) descriptor.configureOutput(binder) } diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt index 83c4c65c..eda83422 100644 --- a/core/src/main/kotlin/Utilities/ServiceLocator.kt +++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt @@ -22,10 +22,7 @@ object ServiceLocator { 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") + val constructor = loadedClass.constructors.firstOrNull { it.parameterTypes.isEmpty() } ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor") val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor) @@ -70,7 +67,7 @@ object ServiceLocator { "jar" -> { val file = JarFile(URL(it.file.substringBefore("!")).toFile()) try { - val jarPath = it.file.substringAfterLast("!").removePrefix("/") + val jarPath = it.file.substringAfterLast("!").removePrefix("/").removeSuffix("/") file.entries() .asSequence() .filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" } diff --git a/core/src/main/kotlin/javadoc/docbase.kt b/core/src/main/kotlin/javadoc/docbase.kt index 118b134a..0bf72ccf 100644 --- a/core/src/main/kotlin/javadoc/docbase.kt +++ b/core/src/main/kotlin/javadoc/docbase.kt @@ -84,7 +84,7 @@ open class DocumentationNodeAdapter(override val module: ModuleNodeAdapter, node // should be extension property but can't because of KT-8745 private fun <T> nodeAnnotations(self: T): List<AnnotationDescAdapter> where T : HasModule, T : HasDocumentationNode - = self.node.annotations.map { AnnotationDescAdapter(self.module, it) } + = self.node.annotations.map { AnnotationDescAdapter(self.module, it) } private fun DocumentationNode.hasAnnotation(klass: KClass<*>) = klass.qualifiedName in annotations.map { it.qualifiedName() } private fun DocumentationNode.hasModifier(name: String) = details(NodeKind.Modifier).any { it.name == name } @@ -94,7 +94,7 @@ class PackageAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : Docum private val allClasses = listOf(node).collectAllTypesRecursively() override fun findClass(className: String?): ClassDoc? = - allClasses.get(className)?.let { ClassDocumentationNodeAdapter(module, it) } + allClasses.get(className)?.let { ClassDocumentationNodeAdapter(module, it) } override fun annotationTypes(): Array<out AnnotationTypeDoc> = emptyArray() override fun annotations(): Array<out AnnotationDesc> = node.members(NodeKind.AnnotationClass).map { AnnotationDescAdapter(module, it) }.toTypedArray() @@ -126,9 +126,9 @@ open class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationN override fun modifierSpecifier(): Int = visibilityModifier or (if (isStatic) STATIC else 0) private val visibilityModifier get() = when { - isPublic() -> PUBLIC - isPrivate() -> PRIVATE - isProtected() -> PROTECTED + isPublic -> PUBLIC + isPrivate -> PRIVATE + isProtected -> PROTECTED else -> 0 } override fun qualifiedName(): String? = node.qualifiedName() @@ -185,24 +185,24 @@ open class TypeAdapter(override val module: ModuleNodeAdapter, override val node override fun isPrimitive(): Boolean = simpleTypeName() in setOf("int", "long", "short", "byte", "char", "double", "float", "boolean", "void") override fun asClassDoc(): ClassDoc? = if (isPrimitive) null else - elementType?.asClassDoc() ?: - when (node.kind) { - in NodeKind.classLike, - NodeKind.ExternalClass, - NodeKind.Exception -> module.classNamed(qualifiedTypeName()) ?: ClassDocumentationNodeAdapter(module, node) - - else -> when { - node.links.isNotEmpty() -> TypeAdapter(module, node.links.first()).asClassDoc() - else -> ClassDocumentationNodeAdapter(module, node) // TODO ? - } + elementType?.asClassDoc() ?: + when (node.kind) { + in NodeKind.classLike, + NodeKind.ExternalClass, + NodeKind.Exception -> module.classNamed(qualifiedTypeName()) ?: ClassDocumentationNodeAdapter(module, node) + + else -> when { + node.links.isNotEmpty() -> TypeAdapter(module, node.links.first()).asClassDoc() + else -> ClassDocumentationNodeAdapter(module, node) // TODO ? } + } override fun asTypeVariable(): TypeVariable? = if (node.kind == NodeKind.TypeParameter) TypeVariableAdapter(module, node) else null override fun asParameterizedType(): ParameterizedType? = - if (node.details(NodeKind.Type).isNotEmpty() && javaLanguageService.getArrayElementType(node) == null) - ParameterizedTypeAdapter(module, node) - else - null + if (node.details(NodeKind.Type).isNotEmpty() && javaLanguageService.getArrayElementType(node) == null) + ParameterizedTypeAdapter(module, node) + else + null override fun asAnnotationTypeDoc(): AnnotationTypeDoc? = if (node.kind == NodeKind.AnnotationClass) AnnotationTypeDocAdapter(module, node) else null override fun asAnnotatedType(): AnnotatedType? = if (node.annotations.isNotEmpty()) AnnotatedTypeAdapter(module, node) else null @@ -258,15 +258,15 @@ class TypeVariableAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : class ParameterizedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), ParameterizedType { override fun typeArguments(): Array<out Type> = node.details(NodeKind.Type).map { TypeVariableAdapter(module, it) }.toTypedArray() override fun superclassType(): Type? = - node.lookupSuperClasses(module) - .firstOrNull { it.kind == NodeKind.Class || it.kind == NodeKind.ExternalClass } - ?.let { ClassDocumentationNodeAdapter(module, it) } + node.lookupSuperClasses(module) + .firstOrNull { it.kind == NodeKind.Class || it.kind == NodeKind.ExternalClass } + ?.let { ClassDocumentationNodeAdapter(module, it) } override fun interfaceTypes(): Array<out Type> = - node.lookupSuperClasses(module) - .filter { it.kind == NodeKind.Interface } - .map { ClassDocumentationNodeAdapter(module, it) } - .toTypedArray() + node.lookupSuperClasses(module) + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() override fun containingType(): Type? = when (node.owner?.kind) { NodeKind.Package -> null @@ -307,7 +307,7 @@ fun classOf(fqName: String, kind: NodeKind = NodeKind.Class) = DocumentationNode } private fun DocumentationNode.hasNonEmptyContent() = - this.content.summary !is ContentEmpty || this.content.description !is ContentEmpty || this.content.sections.isNotEmpty() + this.content.summary !is ContentEmpty || this.content.description !is ContentEmpty || this.content.sections.isNotEmpty() open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ProgramElementAdapter(module, node), ExecutableMemberDoc { @@ -317,17 +317,17 @@ open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: Documentatio override fun thrownExceptions(): Array<out ClassDoc> = emptyArray() // TODO override fun throwsTags(): Array<out ThrowsTag> = - node.content.sections - .filter { it.tag == ContentTags.Exceptions && it.subjectName != null } - .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it.subjectName!!, NodeKind.Exception)), it.children) } - .toTypedArray() + node.content.sections + .filter { it.tag == ContentTags.Exceptions && it.subjectName != null } + .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it.subjectName!!, NodeKind.Exception)), it.children) } + .toTypedArray() override fun isVarArgs(): Boolean = node.details(NodeKind.Parameter).last().hasModifier("vararg") override fun isSynchronized(): Boolean = node.annotations.any { it.name == "synchronized" } override fun paramTags(): Array<out ParamTag> = - collectParamTags(NodeKind.Parameter, sectionFilter = { it.subjectName in parameters().map { it.name() } }) + collectParamTags(NodeKind.Parameter, sectionFilter = { it.subjectName in parameters().map { it.name() } }) override fun thrownExceptionTypes(): Array<out Type> = emptyArray() override fun receiverType(): Type? = receiverNode()?.let { receiver -> TypeAdapter(module, receiver) } @@ -335,14 +335,14 @@ open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: Documentatio override fun signature(): String = node.details(NodeKind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") // TODO it should be FQ types override fun parameters(): Array<out Parameter> = - ((receiverNode()?.let { receiver -> listOf<Parameter>(ReceiverParameterAdapter(module, receiver, this)) } ?: emptyList()) - + node.details(NodeKind.Parameter).map { ParameterAdapter(module, it) } - ).toTypedArray() + ((receiverNode()?.let { receiver -> listOf<Parameter>(ReceiverParameterAdapter(module, receiver, this)) } ?: emptyList()) + + node.details(NodeKind.Parameter).map { ParameterAdapter(module, it) } + ).toTypedArray() override fun typeParameters(): Array<out TypeVariable> = node.details(NodeKind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray() override fun typeParamTags(): Array<out ParamTag> = - collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) + collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) private fun receiverNode() = node.details(NodeKind.Receiver).let { receivers -> when { @@ -400,8 +400,11 @@ class FieldAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : Program } open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNode: DocumentationNode) : ProgramElementAdapter(module, classNode), - Type by TypeAdapter(module, classNode), - ClassDoc { + Type by TypeAdapter(module, classNode), + ClassDoc, + AnnotationTypeDoc { + + override fun elements(): Array<out AnnotationTypeElementDoc>? = emptyArray() // TODO override fun name(): String { val parent = classNode.owner @@ -427,17 +430,17 @@ open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNod override fun enumConstants(): Array<out FieldDoc>? = classNode.members(NodeKind.EnumItem).map { FieldAdapter(module, it) }.toTypedArray() override fun isAbstract(): Boolean = classNode.details(NodeKind.Modifier).any { it.name == "abstract" } override fun interfaceTypes(): Array<out Type> = classNode.lookupSuperClasses(module) - .filter { it.kind == NodeKind.Interface } - .map { ClassDocumentationNodeAdapter(module, it) } - .toTypedArray() + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() override fun interfaces(): Array<out ClassDoc> = classNode.lookupSuperClasses(module) - .filter { it.kind == NodeKind.Interface } - .map { ClassDocumentationNodeAdapter(module, it) } - .toTypedArray() + .filter { it.kind == NodeKind.Interface } + .map { ClassDocumentationNodeAdapter(module, it) } + .toTypedArray() override fun typeParamTags(): Array<out ParamTag> = - collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) + collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) override fun fields(): Array<out FieldDoc> = fields(true) override fun fields(filter: Boolean): Array<out FieldDoc> = classNode.members(NodeKind.Field).map { FieldAdapter(module, it) }.toTypedArray() @@ -477,10 +480,8 @@ open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNod } fun DocumentationNode.lookupSuperClasses(module: ModuleNodeAdapter) = - details(NodeKind.Supertype) - .map { it.links.firstOrNull() } - .map { module.allTypes[it?.qualifiedName()] } - .filterNotNull() + details(NodeKind.Supertype) + .map { it.links.firstOrNull() }.mapNotNull { module.allTypes[it?.qualifiedName()] } fun List<DocumentationNode>.collectAllTypesRecursively(): Map<String, DocumentationNode> { val result = hashMapOf<String, DocumentationNode>() @@ -504,19 +505,19 @@ class ModuleNodeAdapter(val module: DocumentationModule, val reporter: DocErrorR override fun packageNamed(name: String?): PackageDoc? = allPackages[name]?.let { PackageAdapter(this, it) } override fun classes(): Array<out ClassDoc> = - allTypes.values.map { ClassDocumentationNodeAdapter(this, it) }.toTypedArray() + allTypes.values.map { ClassDocumentationNodeAdapter(this, it) }.toTypedArray() override fun options(): Array<out Array<String>> = arrayOf( - arrayOf("-d", outputPath), - arrayOf("-docencoding", "UTF-8"), - arrayOf("-charset", "UTF-8"), - arrayOf("-keywords") + arrayOf("-d", outputPath), + arrayOf("-docencoding", "UTF-8"), + arrayOf("-charset", "UTF-8"), + arrayOf("-keywords") ) override fun specifiedPackages(): Array<out PackageDoc>? = module.members(NodeKind.Package).map { PackageAdapter(this, it) }.toTypedArray() override fun classNamed(qualifiedName: String?): ClassDoc? = - allTypes[qualifiedName]?.let { ClassDocumentationNodeAdapter(this, it) } + allTypes[qualifiedName]?.let { ClassDocumentationNodeAdapter(this, it) } override fun specifiedClasses(): Array<out ClassDoc> = classes() } @@ -526,9 +527,13 @@ private fun DocumentationNodeAdapter.collectParamTags(kind: NodeKind, sectionFil .filter(DocumentationNode::hasNonEmptyContent) .map { ParamTagAdapter(module, this, it.name, true, it.content.children) } - + node.content.sections + + node.content.sections .filter(sectionFilter) - .map { ParamTagAdapter(module, this, it.subjectName ?: "?", true, it.children) } + .map { + ParamTagAdapter(module, this, it.subjectName ?: "?", true, + it.children.filterNot { contentNode -> contentNode is LazyContentBlock } + ) + } ) .distinctBy { it.parameterName } .toTypedArray()
\ No newline at end of file diff --git a/core/src/main/kotlin/javadoc/dokka-adapters.kt b/core/src/main/kotlin/javadoc/dokka-adapters.kt index 483fb3cd..1329876a 100644 --- a/core/src/main/kotlin/javadoc/dokka-adapters.kt +++ b/core/src/main/kotlin/javadoc/dokka-adapters.kt @@ -4,16 +4,19 @@ import com.google.inject.Binder import com.google.inject.Inject import com.sun.tools.doclets.formats.html.HtmlDoclet import org.jetbrains.dokka.* -import org.jetbrains.dokka.Formats.* +import org.jetbrains.dokka.Formats.DefaultAnalysisComponent +import org.jetbrains.dokka.Formats.DefaultAnalysisComponentServices +import org.jetbrains.dokka.Formats.FormatDescriptor +import org.jetbrains.dokka.Formats.KotlinAsJava import org.jetbrains.dokka.Utilities.bind import org.jetbrains.dokka.Utilities.toType -class JavadocGenerator @Inject constructor(val options: DocumentationOptions, val logger: DokkaLogger) : Generator { +class JavadocGenerator @Inject constructor(val configuration: DokkaConfiguration, val logger: DokkaLogger) : Generator { override fun buildPages(nodes: Iterable<DocumentationNode>) { val module = nodes.single() as DocumentationModule - HtmlDoclet.start(ModuleNodeAdapter(module, StandardReporter(logger), options.outputDir)) + HtmlDoclet.start(ModuleNodeAdapter(module, StandardReporter(logger), configuration.outputDir)) } override fun buildOutlines(nodes: Iterable<DocumentationNode>) { diff --git a/core/src/main/resources/dokka/format/kotlin-website-samples.properties b/core/src/main/resources/dokka/format/kotlin-website-samples.properties deleted file mode 100644 index bda616a4..00000000 --- a/core/src/main/resources/dokka/format/kotlin-website-samples.properties +++ /dev/null @@ -1,2 +0,0 @@ -class=org.jetbrains.dokka.Formats.KotlinWebsiteFormatRunnableSamplesDescriptor -description=Generates Kotlin website documentation
\ No newline at end of file diff --git a/core/src/main/resources/dokka/format/kotlin-website.properties b/core/src/main/resources/dokka/format/kotlin-website.properties deleted file mode 100644 index c13e7675..00000000 --- a/core/src/main/resources/dokka/format/kotlin-website.properties +++ /dev/null @@ -1,2 +0,0 @@ -class=org.jetbrains.dokka.Formats.KotlinWebsiteFormatDescriptor -description=Generates Kotlin website documentation
\ No newline at end of file |