<lambda>null1 package org.jetbrains.dokka
2
3 import com.google.inject.Guice
4 import com.google.inject.Injector
5 import com.intellij.openapi.util.Disposer
6 import com.intellij.openapi.vfs.VirtualFileManager
7 import com.intellij.psi.PsiFile
8 import com.intellij.psi.PsiJavaFile
9 import com.intellij.psi.PsiManager
10 import org.jetbrains.dokka.DokkaConfiguration.SourceRoot
11 import org.jetbrains.dokka.Utilities.DokkaAnalysisModule
12 import org.jetbrains.dokka.Utilities.DokkaOutputModule
13 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
14 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
15 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
16 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
17 import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
18 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
19 import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
20 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
21 import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
22 import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
23 import java.io.File
24 import kotlin.system.measureTimeMillis
25
26 class DokkaGenerator(val logger: DokkaLogger,
27 val classpath: List<String>,
28 val sources: List<SourceRoot>,
29 val samples: List<String>,
30 val includes: List<String>,
31 val moduleName: String,
32 val options: DocumentationOptions) {
33
34 private val documentationModule = DocumentationModule(moduleName)
35
36 fun generate() {
37 val sourcesGroupedByPlatform = sources.groupBy { it.platforms.firstOrNull() }
38 for ((platform, roots) in sourcesGroupedByPlatform) {
39 appendSourceModule(platform, roots)
40 }
41 documentationModule.prepareForGeneration(options)
42
43 val timeBuild = measureTimeMillis {
44 logger.info("Generating pages... ")
45 val outputInjector = Guice.createInjector(DokkaOutputModule(options, logger))
46 outputInjector.getInstance(Generator::class.java).buildAll(documentationModule)
47 }
48 logger.info("done in ${timeBuild / 1000} secs")
49 }
50
51 private fun appendSourceModule(defaultPlatform: String?, sourceRoots: List<SourceRoot>) {
52 val sourcePaths = sourceRoots.map { it.path }
53 val environment = createAnalysisEnvironment(sourcePaths)
54
55 logger.info("Module: $moduleName")
56 logger.info("Output: ${File(options.outputDir)}")
57 logger.info("Sources: ${sourcePaths.joinToString()}")
58 logger.info("Classpath: ${environment.classpath.joinToString()}")
59
60 logger.info("Analysing sources and libraries... ")
61 val startAnalyse = System.currentTimeMillis()
62
63 val defaultPlatformAsList = defaultPlatform?.let { listOf(it) }.orEmpty()
64 val defaultPlatformsProvider = object : DefaultPlatformsProvider {
65 override fun getDefaultPlatforms(descriptor: DeclarationDescriptor): List<String> {
66 val containingFilePath = descriptor.sourcePsi()?.containingFile?.virtualFile?.canonicalPath
67 ?.let { File(it).absolutePath }
68 val sourceRoot = containingFilePath?.let { path -> sourceRoots.find { path.startsWith(it.path) } }
69 return sourceRoot?.platforms ?: defaultPlatformAsList
70 }
71 }
72
73 val injector = Guice.createInjector(
74 DokkaAnalysisModule(environment, options, defaultPlatformsProvider, documentationModule.nodeRefGraph, logger))
75
76 buildDocumentationModule(injector, documentationModule, { isNotSample(it) }, includes)
77
78 val timeAnalyse = System.currentTimeMillis() - startAnalyse
79 logger.info("done in ${timeAnalyse / 1000} secs")
80
81 Disposer.dispose(environment)
82 }
83
84 fun createAnalysisEnvironment(sourcePaths: List<String>): AnalysisEnvironment {
85 val environment = AnalysisEnvironment(DokkaMessageCollector(logger))
86
87 environment.apply {
88 //addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre())
89 // addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
90 for (element in this@DokkaGenerator.classpath) {
91 addClasspath(File(element))
92 }
93
94 addSources(sourcePaths)
95 addSources(this@DokkaGenerator.samples)
96
97 loadLanguageVersionSettings(options.languageVersion, options.apiVersion)
98 }
99
100 return environment
101 }
102
103 fun isNotSample(file: PsiFile): Boolean {
104 val sourceFile = File(file.virtualFile!!.path)
105 return samples.none { sample ->
106 val canonicalSample = File(sample).canonicalPath
107 val canonicalSource = sourceFile.canonicalPath
108 canonicalSource.startsWith(canonicalSample)
109 }
110 }
111 }
112
113 class DokkaMessageCollector(val logger: DokkaLogger) : MessageCollector {
clearnull114 override fun clear() {
115 seenErrors = false
116 }
117
118 private var seenErrors = false
119
reportnull120 override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
121 if (severity == CompilerMessageSeverity.ERROR) {
122 seenErrors = true
123 }
124 logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location))
125 }
126
hasErrorsnull127 override fun hasErrors() = seenErrors
128 }
129
130 fun buildDocumentationModule(injector: Injector,
131 documentationModule: DocumentationModule,
132 filesToDocumentFilter: (PsiFile) -> Boolean = { file -> true },
133 includes: List<String> = listOf()) {
134
135 val coreEnvironment = injector.getInstance(KotlinCoreEnvironment::class.java)
136 val fragmentFiles = coreEnvironment.getSourceFiles().filter(filesToDocumentFilter)
137
138 val resolutionFacade = injector.getInstance(DokkaResolutionFacade::class.java)
139 val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzer::class.java)
140 analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles)
141
142 val fragments = fragmentFiles
<lambda>null143 .map { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) }
144 .filterNotNull()
145 .distinct()
146
147 val packageDocs = injector.getInstance(PackageDocs::class.java)
148 for (include in includes) {
149 packageDocs.parse(include, fragments)
150 }
151 if (documentationModule.content.isEmpty()) {
<lambda>null152 documentationModule.updateContent {
153 for (node in packageDocs.moduleContent.children) {
154 append(node)
155 }
156 }
157 }
158
159 parseJavaPackageDocs(packageDocs, coreEnvironment)
160
<lambda>null161 with(injector.getInstance(DocumentationBuilder::class.java)) {
162 documentationModule.appendFragments(fragments, packageDocs.packageContent,
163 injector.getInstance(PackageDocumentationBuilder::class.java))
164
165 propagateExtensionFunctionsToSubclasses(fragments, resolutionFacade)
166 }
167
168 val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter)
<lambda>null169 with(injector.getInstance(JavaDocumentationBuilder::class.java)) {
170 javaFiles.map { appendFile(it, documentationModule, packageDocs.packageContent) }
171 }
172 }
173
parseJavaPackageDocsnull174 fun parseJavaPackageDocs(packageDocs: PackageDocs, coreEnvironment: KotlinCoreEnvironment) {
175 val contentRoots = coreEnvironment.configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
176 ?.filterIsInstance<JavaSourceRoot>()
177 ?.map { it.file }
178 ?: listOf()
179 contentRoots.forEach { root ->
180 root.walkTopDown().filter { it.name == "overview.html" }.forEach {
181 packageDocs.parseJava(it.path, it.relativeTo(root).parent.replace("/", "."))
182 }
183 }
184 }
185
186
KotlinCoreEnvironmentnull187 fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> {
188 val sourceRoots = configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
189 ?.filterIsInstance<JavaSourceRoot>()
190 ?.map { it.file }
191 ?: listOf()
192
193 val result = arrayListOf<PsiJavaFile>()
194 val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
195 sourceRoots.forEach { sourceRoot ->
196 sourceRoot.absoluteFile.walkTopDown().forEach {
197 val vFile = localFileSystem.findFileByPath(it.path)
198 if (vFile != null) {
199 val psiFile = PsiManager.getInstance(project).findFile(vFile)
200 if (psiFile is PsiJavaFile) {
201 result.add(psiFile)
202 }
203 }
204 }
205 }
206 return result
207 }
208