Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dd2ca3c
Update to Kotlin 2.2.20
nicolas-guichard Jul 3, 2026
08e2990
scip-kotlinc: Populate SymbolInformation.Kind
nicolas-guichard Jul 3, 2026
c88a8e7
scip-kotlinc: Rework getters and setters to be property children
nicolas-guichard Jul 3, 2026
bce15ae
scip-kotlinc: Add enclosing_symbol field
nicolas-guichard Jul 3, 2026
d2c4575
scip-kotlinc: Ignore FirFileSymbols without warning
nicolas-guichard Jul 3, 2026
111cd71
scip-kotlinc: Clear AnalyzerCheckers.visitors after consuming them
nicolas-guichard Jul 3, 2026
0118cd1
Update to Kotlin 2.3.10
rvandermeulen Jul 3, 2026
a461284
scip-kotlinc: Fix compiler warnings
rvandermeulen Jul 3, 2026
ffb3acc
scip-kotlinc: Fix PostAnalysisExtension to use the compilation's mess…
rvandermeulen Jul 3, 2026
c4fdd4d
scip-kotlinc: Add test for lambda parameters
rvandermeulen Jul 3, 2026
b426a81
scip-kotlinc: Add tests for local functions and user-defined class re…
rvandermeulen Jul 3, 2026
4f52297
scip-kotlinc: Fix displayName() for type aliases and type parameters
rvandermeulen Jul 3, 2026
1d83c74
scip-kotlinc: Fix extension receiver, enum entry, and is/as type occu…
rvandermeulen Jul 3, 2026
43493f3
scip-kotlinc: Add tests for multiple supertypes and overload disambig…
rvandermeulen Jul 3, 2026
6cd3fe9
scip-kotlinc: Fix misleading LineMap docstrings
rvandermeulen Jul 3, 2026
f5de324
scip-kotlinc: Extend enclosing_range test coverage
rvandermeulen Jul 3, 2026
85d815c
Update to Kotlin 2.3.20
rvandermeulen Jul 3, 2026
5aebd5e
Update to Kotlin 2.4.0
nicolas-guichard Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
clikt = "5.1.0"
gradle-api = "8.11.1"
junit-jupiter = "5.11.4"
kctfork = "0.7.1"
kctfork = "0.13.0"
kotest = "6.2.1"
kotlin = "2.2.0"
kotlin = "2.4.0"
kotlinx-serialization = "1.11.0"
lombok = "1.18.46"
maven-plugin-annotations = "3.15.2"
Expand Down
4 changes: 0 additions & 4 deletions scip-kotlinc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ dependencies {
testImplementation(libs.kctfork.core)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.add("-Xcontext-parameters")
}

tasks.named<Test>("test") {
maxHeapSize = "2g"
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ val KEY_SOURCES = CompilerConfigurationKey<Path>(VAL_SOURCES)
const val VAL_TARGET = "targetroot"
val KEY_TARGET = CompilerConfigurationKey<Path>(VAL_TARGET)

const val PLUGIN_ID = "scip-kotlinc"

@OptIn(ExperimentalCompilerApi::class)
class AnalyzerCommandLineProcessor : CommandLineProcessor {
override val pluginId: String = "scip-kotlinc"
override val pluginId: String = PLUGIN_ID
override val pluginOptions: Collection<AbstractCliOption> =
listOf(
CliOption(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : Compile
FirExtensionRegistrarAdapter.registerExtension(AnalyzerFirExtensionRegistrar(options))
IrGenerationExtension.registerExtension(
PostAnalysisExtension(
configuration = configuration,
sourceRoot = options.sourceroot,
targetRoot = options.targetroot,
callback = callback,
)
)
}

override val pluginId = PLUGIN_ID

override val supportsK2: Boolean
get() = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ class LineMap(private val file: FirFile) {
private fun offsetToLineAndCol(offset: Int): Pair<Int, Int>? =
file.sourceFileLinesMapping?.getLineAndColumnByOffset(offset)

/** Returns the non-0-based line number for a given offset */
/** Returns the 1-based line number for a given offset (subtract 1 for protobuf). */
fun lineNumberForOffset(offset: Int): Int =
file.sourceFileLinesMapping?.getLineByOffset(offset)?.let { it + 1 } ?: 0

/** Returns the non-0-based column number for a given offset */
/** Returns the 0-based column number for a given offset. */
fun columnForOffset(offset: Int): Int = offsetToLineAndCol(offset)?.second ?: 0

/** Returns the non-0-based start character */
/** Returns the 0-based start character. */
fun startCharacter(element: KtSourceElement): Int =
offsetToLineAndCol(element.startOffset)?.second ?: 0

/** Returns the non-0-based end character */
/** Returns the 0-based end character. */
fun endCharacter(element: KtSourceElement): Int =
startCharacter(element) + nameForOffset(element).length

/** Returns the non-0-based line number */
/** Returns the 1-based line number (subtract 1 for protobuf). */
fun lineNumber(element: KtSourceElement): Int =
file.sourceFileLinesMapping?.getLineByOffset(element.startOffset)?.let { it + 1 } ?: 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.scip_code.scip_java.shared.ScipShardWriter
* outside the source root are skipped with a stderr warning.
*/
class PostAnalysisExtension(
private val configuration: CompilerConfiguration,
private val sourceRoot: Path,
private val targetRoot: Path,
private val callback: (Document) -> Unit,
Expand All @@ -45,6 +46,7 @@ class PostAnalysisExtension(
} catch (e: Exception) {
handleException(e)
}
AnalyzerCheckers.visitors.clear()
}

private fun scipShardPathForFile(file: KtSourceFile): Path? {
Expand All @@ -60,11 +62,10 @@ class PostAnalysisExtension(
}

private val messageCollector =
CompilerConfiguration()
.get(
CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY,
PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false),
)
configuration.get(
CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY,
PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false)
)

private fun handleException(e: Exception) {
val writer =
Expand All @@ -73,7 +74,7 @@ class PostAnalysisExtension(
val buf = StringBuffer()

override fun close() =
messageCollector.report(CompilerMessageSeverity.EXCEPTION, buf.toString())
messageCollector.report(CompilerMessageSeverity.WARNING, buf.toString())

override fun flush() = Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import java.nio.file.Paths
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirPackageDirective
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenSymbolsSafe
import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol
import org.jetbrains.kotlin.fir.analysis.getChild
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.renderer.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
Expand All @@ -19,6 +22,7 @@ import org.jetbrains.kotlin.text
import org.scip_code.scip.Document
import org.scip_code.scip.Occurrence
import org.scip_code.scip.SymbolInformation
import org.scip_code.scip.SymbolInformation.Kind
import org.scip_code.scip.SymbolRole
import org.scip_code.scip.relationship
import org.scip_code.scip.signature
Expand All @@ -39,26 +43,26 @@ class ScipTextDocumentBuilder(

fun build(): Document = documentBuilder.build("kotlin", relativePath(), fileText)

context(context: CheckerContext)
fun emitScipData(
firBasedSymbol: FirBasedSymbol<*>?,
symbol: Symbol,
element: KtSourceElement,
isDefinition: Boolean,
context: CheckerContext,
enclosingSource: KtSourceElement? = null,
) {
documentBuilder.addOccurrence(occurrence(symbol, element, isDefinition, enclosingSource))
if (isDefinition) {
documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element, context))
documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element))
}
}

@OptIn(SymbolInternals::class)
context(context: CheckerContext)
private fun symbolInformation(
firBasedSymbol: FirBasedSymbol<*>?,
symbol: Symbol,
element: KtSourceElement,
context: CheckerContext,
): SymbolInformation {
val supers =
when (firBasedSymbol) {
Expand All @@ -68,7 +72,7 @@ class ScipTextDocumentBuilder(
.mapNotNull { it.toClassLikeSymbol(firBasedSymbol.moduleData.session) }
.flatMap { cache[it] }
is FirFunctionSymbol<*> ->
firBasedSymbol.directOverriddenSymbolsSafe(context).flatMap { cache[it] }
firBasedSymbol.directOverriddenSymbolsSafe().flatMap { cache[it] }
else -> emptyList()
}
return symbolInformation {
Expand All @@ -84,6 +88,8 @@ class ScipTextDocumentBuilder(
}
docComment(firBasedSymbol.fir)?.let { documentation += it }
}
this.kind = scipKind(firBasedSymbol?.fir)
this.enclosingSymbol = context.containingDeclarations.lastOrNull()?.let {cache[it].last().toString() } ?: ""
for (parent in supers) {
relationships += relationship {
this.symbol = parent.toString()
Expand Down Expand Up @@ -146,7 +152,7 @@ class ScipTextDocumentBuilder(
bodyRenderer = null,
propertyAccessorRenderer = null,
callArgumentsRenderer = FirCallNoArgumentsRenderer(),
modifierRenderer = FirAllModifierRenderer(),
modifierRenderer = FirAllModifierRenderer(FirModifierRenderer.StaticPolicy.Default),
callableSignatureRenderer = FirCallableSignatureRendererForReadability(),
declarationRenderer = FirDeclarationRenderer("local "),
)
Expand All @@ -159,6 +165,23 @@ class ScipTextDocumentBuilder(
return stripKdoc(kdoc).ifEmpty { null }
}

private fun scipKind(element: FirElement?): Kind =
when (element) {
is FirClass if element.isInterface -> Kind.Interface
is FirTypeAlias -> Kind.TypeAlias
is FirClassLikeDeclaration -> Kind.Class
is FirConstructor -> Kind.Constructor
is FirTypeParameter -> Kind.TypeParameter
is FirValueParameter -> Kind.Parameter
is FirField -> Kind.Field
is FirProperty -> Kind.Property
is FirEnumEntry -> Kind.EnumMember
is FirVariable -> Kind.Variable
is FirCallableDeclaration -> Kind.Method
is FirPackageDirective -> Kind.Package
else -> Kind.UNRECOGNIZED
}

/** Strips the `/**`, leading `*`s, and `*/` from a kdoc block, returning just the body text. */
private fun stripKdoc(kdoc: String): String {
if (kdoc.isEmpty()) return kdoc
Expand All @@ -184,14 +207,15 @@ class ScipTextDocumentBuilder(
}

companion object {
@OptIn(SymbolInternals::class)
@OptIn(SymbolInternals::class, RenderingInternals::class)
private fun displayName(firBasedSymbol: FirBasedSymbol<*>): String =
when (firBasedSymbol) {
is FirClassSymbol -> firBasedSymbol.classId.shortClassName.asString()
is FirClassLikeSymbol -> firBasedSymbol.classId.shortClassName.asString()
is FirPropertyAccessorSymbol -> firBasedSymbol.fir.propertySymbol.name.asString()
is FirFunctionSymbol -> firBasedSymbol.callableId.callableName.asString()
is FirPropertySymbol -> firBasedSymbol.callableId.callableName.asString()
is FirPropertySymbol -> firBasedSymbol.callableIdForRendering.callableName.asString()
is FirVariableSymbol -> firBasedSymbol.name.asString()
is FirTypeParameterSymbol -> firBasedSymbol.name.asString()
else -> firBasedSymbol.toString()
}
}
Expand Down
Loading