Skip to content

Commit 5318c13

Browse files
WIP
1 parent 28f00b0 commit 5318c13

18 files changed

Lines changed: 2464 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package demo
2+
3+
fun main(args: Array<String>) {
4+
// Test some Kotlin 1.9 features
5+
println(args[0])
6+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import com.sourcegraph.semanticdb_javac.Semanticdb
4+
import java.io.PrintWriter
5+
import java.io.Writer
6+
import java.nio.file.Files
7+
import java.nio.file.Path
8+
import java.nio.file.Paths
9+
import kotlin.contracts.ExperimentalContracts
10+
import org.jetbrains.kotlin.analyzer.AnalysisResult
11+
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
12+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
13+
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
14+
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
15+
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
16+
import org.jetbrains.kotlin.config.CompilerConfiguration
17+
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
18+
import org.jetbrains.kotlin.psi.*
19+
import org.jetbrains.kotlin.resolve.BindingTrace
20+
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
21+
22+
@ExperimentalContracts
23+
class Analyzer(
24+
val sourceroot: Path,
25+
val targetroot: Path,
26+
val callback: (Semanticdb.TextDocument) -> Unit
27+
) : AnalysisHandlerExtension {
28+
private val globals = GlobalSymbolsCache()
29+
30+
private val messageCollector =
31+
CompilerConfiguration()
32+
.get(
33+
CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY,
34+
PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false))
35+
36+
override fun analysisCompleted(
37+
project: Project,
38+
module: ModuleDescriptor,
39+
bindingTrace: BindingTrace,
40+
files: Collection<KtFile>
41+
): AnalysisResult? =
42+
try {
43+
val resolver = DescriptorResolver(bindingTrace).also { globals.resolver = it }
44+
for (file in files) {
45+
try {
46+
val lineMap = LineMap(project, file)
47+
val document =
48+
SemanticdbVisitor(sourceroot, resolver, file, lineMap, globals).build()
49+
semanticdbOutPathForFile(file)?.apply {
50+
val builder = Semanticdb.TextDocuments.newBuilder()
51+
builder.addDocuments(document)
52+
Files.write(this, builder.build().toByteArray())
53+
}
54+
callback(document)
55+
} catch (e: Exception) {
56+
handleException(e)
57+
}
58+
}
59+
60+
super.analysisCompleted(project, module, bindingTrace, files)
61+
} catch (e: Exception) {
62+
handleException(e)
63+
super.analysisCompleted(project, module, bindingTrace, files)
64+
}
65+
66+
private fun semanticdbOutPathForFile(file: KtFile): Path? {
67+
val normalizedPath = Paths.get(file.virtualFilePath).normalize()
68+
if (normalizedPath.startsWith(sourceroot)) {
69+
val relative = sourceroot.relativize(normalizedPath)
70+
val filename = relative.fileName.toString() + ".semanticdb"
71+
val semanticdbOutPath =
72+
targetroot
73+
.resolve("META-INF")
74+
.resolve("semanticdb")
75+
.resolve(relative)
76+
.resolveSibling(filename)
77+
78+
Files.createDirectories(semanticdbOutPath.parent)
79+
return semanticdbOutPath
80+
}
81+
System.err.println(
82+
"given file is not under the sourceroot.\n\tSourceroot: $sourceroot\n\tFile path: ${file.virtualFilePath}\n\tNormalized file path: $normalizedPath")
83+
return null
84+
}
85+
86+
private fun handleException(e: Exception) {
87+
val writer =
88+
PrintWriter(
89+
object : Writer() {
90+
val buf = StringBuffer()
91+
override fun close() =
92+
messageCollector.report(CompilerMessageSeverity.EXCEPTION, buf.toString())
93+
94+
override fun flush() = Unit
95+
override fun write(data: CharArray, offset: Int, len: Int) {
96+
buf.append(data, offset, len)
97+
}
98+
},
99+
false)
100+
writer.println("Exception in semanticdb-kotlin compiler plugin:")
101+
e.printStackTrace(writer)
102+
writer.println(
103+
"Please report a bug to https://github.com/sourcegraph/lsif-kotlin with the stack trace above.")
104+
writer.close()
105+
}
106+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import java.nio.file.Path
4+
import java.nio.file.Paths
5+
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
6+
import org.jetbrains.kotlin.compiler.plugin.CliOption
7+
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
8+
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
9+
import org.jetbrains.kotlin.config.CompilerConfiguration
10+
import org.jetbrains.kotlin.config.CompilerConfigurationKey
11+
12+
const val VAL_SOURCES = "sourceroot"
13+
val KEY_SOURCES = CompilerConfigurationKey<Path>(VAL_SOURCES)
14+
15+
const val VAL_TARGET = "targetroot"
16+
val KEY_TARGET = CompilerConfigurationKey<Path>(VAL_TARGET)
17+
18+
@OptIn(ExperimentalCompilerApi::class)
19+
class AnalyzerCommandLineProcessor : CommandLineProcessor {
20+
override val pluginId: String = "semanticdb-kotlinc"
21+
override val pluginOptions: Collection<AbstractCliOption> =
22+
listOf(
23+
CliOption(
24+
VAL_SOURCES,
25+
"<path>",
26+
"the absolute path to the root of the Kotlin sources",
27+
required = true),
28+
CliOption(
29+
VAL_TARGET,
30+
"<path>",
31+
"the absolute path to the directory where to generate SemanticDB files.",
32+
required = true))
33+
34+
override fun processOption(
35+
option: AbstractCliOption,
36+
value: String,
37+
configuration: CompilerConfiguration
38+
) {
39+
when (option.optionName) {
40+
VAL_SOURCES -> configuration.put(KEY_SOURCES, Paths.get(value))
41+
VAL_TARGET -> configuration.put(KEY_TARGET, Paths.get(value))
42+
}
43+
}
44+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import com.sourcegraph.semanticdb_javac.Semanticdb
4+
import java.lang.IllegalArgumentException
5+
import kotlin.contracts.ExperimentalContracts
6+
import org.jetbrains.kotlin.com.intellij.mock.MockProject
7+
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
8+
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
9+
import org.jetbrains.kotlin.config.CompilerConfiguration
10+
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
11+
12+
@OptIn(ExperimentalCompilerApi::class)
13+
@ExperimentalContracts
14+
class AnalyzerRegistrar(private val callback: (Semanticdb.TextDocument) -> Unit = {}) :
15+
ComponentRegistrar {
16+
override fun registerProjectComponents(
17+
project: MockProject,
18+
configuration: CompilerConfiguration
19+
) {
20+
AnalysisHandlerExtension.registerExtension(
21+
project,
22+
Analyzer(
23+
sourceroot = configuration[KEY_SOURCES]
24+
?: throw IllegalArgumentException("configuration key $KEY_SOURCES missing"),
25+
targetroot = configuration[KEY_TARGET]
26+
?: throw IllegalArgumentException("configuration key $KEY_TARGET missing"),
27+
callback = callback))
28+
}
29+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
4+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
5+
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
6+
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
7+
8+
fun DeclarationDescriptor.isObjectDeclaration(): Boolean =
9+
this is ClassDescriptor && this.visibility == DescriptorVisibilities.LOCAL
10+
11+
fun DeclarationDescriptor.isLocalVariable(): Boolean = this is LocalVariableDescriptor
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
4+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
5+
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
6+
import org.jetbrains.kotlin.psi.*
7+
import org.jetbrains.kotlin.resolve.BindingContext
8+
import org.jetbrains.kotlin.resolve.BindingTrace
9+
import org.jetbrains.kotlin.types.KotlinType
10+
11+
class DescriptorResolver(/* leave public for debugging */ val bindingTrace: BindingTrace) {
12+
fun fromDeclaration(declaration: KtDeclaration): Sequence<DeclarationDescriptor> = sequence {
13+
val descriptor = bindingTrace[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
14+
if (descriptor is ValueParameterDescriptor) {
15+
bindingTrace[BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor]?.let { yield(it) }
16+
}
17+
descriptor?.let { yield(it) }
18+
}
19+
20+
fun syntheticConstructor(klass: KtClass): ConstructorDescriptor? =
21+
bindingTrace[BindingContext.CONSTRUCTOR, klass]
22+
23+
fun fromReference(reference: KtReferenceExpression): DeclarationDescriptor? =
24+
bindingTrace[BindingContext.REFERENCE_TARGET, reference]
25+
26+
fun fromTypeReference(reference: KtTypeReference): KotlinType =
27+
bindingTrace[BindingContext.TYPE, reference]
28+
?: bindingTrace[BindingContext.ABBREVIATED_TYPE, reference]!!
29+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
import org.jetbrains.kotlin.com.intellij.navigation.NavigationItem
4+
import org.jetbrains.kotlin.com.intellij.openapi.editor.Document
5+
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
6+
import org.jetbrains.kotlin.com.intellij.psi.PsiDocumentManager
7+
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
8+
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils
9+
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils.LineAndColumn
10+
import org.jetbrains.kotlin.psi.KtFile
11+
import org.jetbrains.kotlin.psi.KtPropertyAccessor
12+
13+
/** Maps between an element and its identifier positions */
14+
class LineMap(project: Project, file: KtFile) {
15+
private val document: Document = PsiDocumentManager.getInstance(project).getDocument(file)!!
16+
17+
private fun offsetToLineAndCol(offset: Int): LineAndColumn =
18+
PsiDiagnosticUtils.offsetToLineAndColumn(document, offset)
19+
20+
/** Returns the non-0-based start character */
21+
fun startCharacter(element: PsiElement): Int = offsetToLineAndCol(element.textOffset).column
22+
23+
/** Returns the non-0-based end character */
24+
fun endCharacter(element: PsiElement): Int =
25+
startCharacter(element) + nameForOffset(element).length
26+
27+
/** Returns the non-0-based line number */
28+
fun lineNumber(element: PsiElement): Int = document.getLineNumber(element.textOffset) + 1
29+
30+
companion object {
31+
fun nameForOffset(element: PsiElement): String =
32+
when (element) {
33+
is KtPropertyAccessor -> element.namePlaceholder.text
34+
is NavigationItem -> element.name ?: element.text
35+
else -> element.text
36+
}
37+
}
38+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.sourcegraph.semanticdb_kotlinc
2+
3+
@JvmInline
4+
value class Symbol(private val symbol: String) {
5+
companion object {
6+
val NONE = Symbol("")
7+
val ROOT_PACKAGE = Symbol("_root_/")
8+
val EMPTY_PACKAGE = Symbol("_empty_/")
9+
10+
fun createGlobal(owner: Symbol, desc: SemanticdbSymbolDescriptor): Symbol =
11+
when {
12+
desc == SemanticdbSymbolDescriptor.NONE -> NONE
13+
owner != ROOT_PACKAGE -> Symbol(owner.symbol + desc.encode().symbol)
14+
else -> desc.encode()
15+
}
16+
17+
fun createLocal(i: Int) = Symbol("local$i")
18+
}
19+
20+
fun isGlobal() = !isLocal()
21+
22+
fun isLocal() = symbol.startsWith("local")
23+
24+
override fun toString(): String = symbol
25+
}
26+
27+
fun String.symbol(): Symbol = Symbol(this)
28+
29+
data class SemanticdbSymbolDescriptor(
30+
val kind: Kind,
31+
val name: String,
32+
val disambiguator: String = "()"
33+
) {
34+
companion object {
35+
val NONE = SemanticdbSymbolDescriptor(Kind.NONE, "")
36+
37+
private fun encodeName(name: String): String {
38+
if (name.isEmpty()) return "``"
39+
val isStartOk = Character.isJavaIdentifierStart(name[0])
40+
var isPartsOk = true
41+
var i = 1
42+
while (isPartsOk && i < name.length) {
43+
isPartsOk = Character.isJavaIdentifierPart(name[i])
44+
i++
45+
}
46+
return if (isStartOk && isPartsOk) name else "`$name`"
47+
}
48+
}
49+
50+
enum class Kind {
51+
NONE,
52+
TERM,
53+
METHOD,
54+
TYPE,
55+
PACKAGE,
56+
PARAMETER,
57+
TYPE_PARAMETER
58+
}
59+
60+
fun encode() =
61+
Symbol(
62+
when (kind) {
63+
Kind.NONE -> ""
64+
Kind.TERM -> "${encodeName(name)}."
65+
Kind.METHOD -> "${encodeName(name)}${disambiguator}."
66+
Kind.TYPE -> "${encodeName(name)}#"
67+
Kind.PACKAGE -> "${encodeName(name)}/"
68+
Kind.PARAMETER -> "(${encodeName(name)})"
69+
Kind.TYPE_PARAMETER -> "[${encodeName(name)}]"
70+
})
71+
}

0 commit comments

Comments
 (0)