diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 93d930c20..a028c2915 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle index 21b68107f..e097254af 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '2.2.0' + id 'org.jetbrains.kotlin.jvm' version '2.4.0' } java { toolchain { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index d0a96fa86..b559907c9 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.2.0' + id 'org.jetbrains.kotlin.jvm' version '2.4.0' } kotlin { jvmToolchain(17) diff --git a/scip-kotlinc/build.gradle.kts b/scip-kotlinc/build.gradle.kts index 699d24dd9..aad935acf 100644 --- a/scip-kotlinc/build.gradle.kts +++ b/scip-kotlinc/build.gradle.kts @@ -24,10 +24,6 @@ dependencies { testImplementation(libs.kctfork.core) } -tasks.withType().configureEach { - compilerOptions.freeCompilerArgs.add("-Xcontext-parameters") -} - tasks.named("test") { maxHeapSize = "2g" } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index 37ece4b57..1251f3a8f 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -11,19 +11,23 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessExpressionChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirResolvedQualifierChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirTypeOperatorCallChecker -import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isCompanion import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticFunctionSymbol +import org.jetbrains.kotlin.fir.resolve.getContainingClassSymbol import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.resolve.toClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -36,6 +40,15 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio element.treeStructure .findChildByType(element.lighterASTNode, KtTokens.IDENTIFIER) ?.toKtLightSourceElement(element.treeStructure) ?: element + + context(context: CheckerContext) + private fun ScipVisitor.emitTypeRef(typeRef: FirTypeRef) { + val klass = typeRef.toClassLikeSymbol(context.session) + val source = typeRef.source + if (klass != null && source != null && source.kind !is KtFakeSourceElementKind) { + visitClassReference(klass, getIdentifier(source)) + } + } } override val declarationCheckers: DeclarationCheckers @@ -48,8 +61,10 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio Set = setOf(SemanticQualifiedAccessExpressionChecker()) - override val typeOperatorCallCheckers: - Set = + override val resolvedQualifierCheckers: Set = + setOf(SemanticResolvedQualifierChecker()) + + override val typeOperatorCallCheckers: Set = setOf(SemanticClassReferenceExpressionChecker()) } @@ -71,6 +86,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio override val typeAliasCheckers: Set = setOf(SemanticTypeAliasChecker()) override val propertyAccessorCheckers: Set = setOf(SemanticPropertyAccessorChecker()) + override val enumEntryCheckers: Set = setOf(SemanticEnumEntryChecker()) } private class SemanticFileChecker(private val sourceroot: Path) : @@ -134,7 +150,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio if (names != null) { eachFqNameElement(fqName, source.treeStructure, names) { fqName, name -> - visitor?.visitPackage(fqName, name, context) + visitor?.visitPackage(fqName, name) } } } @@ -161,13 +177,13 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio ) if (klass != null) { - visitor?.visitClassReference(klass, name, context) + visitor?.visitClassReference(klass, name) } else if (callables.isNotEmpty()) { for (callable in callables) { - visitor?.visitCallableReference(callable, name, context) + visitor?.visitCallableReference(callable, name) } } else { - visitor?.visitPackage(fqName, name, context) + visitor?.visitPackage(fqName, name) } } } @@ -179,7 +195,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirClassLikeDeclaration) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] val objectKeyword = if (declaration is FirAnonymousObject) { @@ -189,20 +205,32 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio } else { null } + val identifierSource = getIdentifier(source) + // For unnamed companion objects, getIdentifier() falls back to source (no IDENTIFIER + // token). Use the 'companion' keyword as the range instead. The COMPANION_KEYWORD is + // inside a MODIFIER_LIST child, so we use findDescendantByType instead of + // findChildByType. + val companionKeyword = + if ( + identifierSource === source && + declaration is FirRegularClass && + declaration.isCompanion + ) { + source.treeStructure + .findDescendantByType(source.lighterASTNode, KtTokens.COMPANION_KEYWORD) + ?.toKtLightSourceElement(source.treeStructure) + } else { + null + } visitor?.visitClassOrObject( declaration, - objectKeyword ?: getIdentifier(source), - context, + objectKeyword ?: companionKeyword ?: identifierSource, enclosingSource = source, ) if (declaration is FirClass) { for (superType in declaration.superTypeRefs) { - val superSymbol = superType.toClassLikeSymbol(context.session) - val superSource = superType.source - if (superSymbol != null && superSource != null) { - visitor?.visitClassReference(superSymbol, superSource, context) - } + visitor?.emitTypeRef(superType) } } } @@ -212,7 +240,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirConstructor) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] if (declaration.isPrimary) { @@ -238,14 +266,12 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio visitor?.visitPrimaryConstructor( declaration, constructorKeyboard ?: objectKeyword ?: getIdentifier(klassSource), - context, enclosingSource = source, ) } else { visitor?.visitSecondaryConstructor( declaration, getIdentifier(source), - context, enclosingSource = source, ) } @@ -254,24 +280,17 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticSimpleFunctionChecker : FirSimpleFunctionChecker(MppCheckerKind.Common) { context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirSimpleFunction) { + override fun check(declaration: FirNamedFunction) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitNamedFunction( declaration, getIdentifier(source), - context, enclosingSource = source, ) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) - } + visitor?.emitTypeRef(declaration.returnTypeRef) + declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } } } @@ -280,9 +299,9 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirAnonymousFunction) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitNamedFunction(declaration, source, context, enclosingSource = source) + visitor?.visitNamedFunction(declaration, source, enclosingSource = source) } } @@ -290,22 +309,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirProperty) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitProperty( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) - } + visitor?.visitProperty(declaration, getIdentifier(source), enclosingSource = source) + visitor?.emitTypeRef(declaration.returnTypeRef) + declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } } } @@ -313,22 +321,10 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirValueParameter) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitParameter( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) - } + visitor?.visitParameter(declaration, getIdentifier(source), enclosingSource = source) + visitor?.emitTypeRef(declaration.returnTypeRef) } } @@ -336,12 +332,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirTypeParameter) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitTypeParameter( declaration, getIdentifier(source), - context, enclosingSource = source, ) } @@ -351,14 +346,9 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirTypeAlias) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitTypeAlias( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) + visitor?.visitTypeAlias(declaration, getIdentifier(source), enclosingSource = source) } } @@ -367,7 +357,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirPropertyAccessor) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] val identifierSource = if (declaration.isGetter) { @@ -382,12 +372,30 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio getIdentifier(source) } - visitor?.visitPropertyAccessor( - declaration, - identifierSource, - context, - enclosingSource = source, - ) + visitor?.visitPropertyAccessor(declaration, identifierSource, enclosingSource = source) + } + } + + private class SemanticEnumEntryChecker : FirEnumEntryChecker(MppCheckerKind.Common) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirEnumEntry) { + val source = declaration.source ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return + val visitor = visitors[ktFile] + visitor?.visitEnumEntry(declaration, getIdentifier(source), enclosingSource = source) + } + } + + private class SemanticResolvedQualifierChecker : + FirResolvedQualifierChecker(MppCheckerKind.Common) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(expression: FirResolvedQualifier) { + val symbol = expression.symbol ?: return + val source = expression.source ?: return + if (source.kind is KtFakeSourceElementKind) return + val ktFile = context.containingFileSymbol?.sourceFile ?: return + val visitor = visitors[ktFile] + visitor?.visitClassReference(symbol, getIdentifier(source)) } } @@ -401,27 +409,19 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio return } - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitSimpleNameExpression( - calleeReference, - getIdentifier(calleeReference.source ?: source), - context, - ) + val identifierSource = getIdentifier(calleeReference.source ?: source) + visitor?.visitSimpleNameExpression(calleeReference, identifierSource) val resolvedSymbol = calleeReference.resolvedSymbol if ( resolvedSymbol.origin == FirDeclarationOrigin.SamConstructor && resolvedSymbol is FirSyntheticFunctionSymbol ) { - val referencedKlass = - resolvedSymbol.resolvedReturnType.toClassLikeSymbol(context.session) + val referencedKlass = resolvedSymbol.resolvedReturnType.toClassLikeSymbol() if (referencedKlass != null) { - visitor?.visitClassReference( - referencedKlass, - getIdentifier(calleeReference.source ?: source), - context, - ) + visitor?.visitClassReference(referencedKlass, identifierSource) } } @@ -429,18 +429,10 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio // symbols if (resolvedSymbol is FirPropertySymbol) { resolvedSymbol.getterSymbol?.let { - visitor?.visitCallableReference( - it, - getIdentifier(calleeReference.source ?: source), - context, - ) + visitor?.visitCallableReference(it, identifierSource) } resolvedSymbol.setterSymbol?.let { - visitor?.visitCallableReference( - it, - getIdentifier(calleeReference.source ?: source), - context, - ) + visitor?.visitCallableReference(it, identifierSource) } } } @@ -452,16 +444,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio override fun check(expression: FirTypeOperatorCall) { val typeRef = expression.conversionTypeRef val source = typeRef.source ?: return - val classSymbol = - expression.conversionTypeRef.toClassLikeSymbol(context.session) ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val classSymbol = typeRef.toClassLikeSymbol(context.session) ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitClassReference( - classSymbol, - getIdentifier(expression.conversionTypeRef.source ?: source), - context, - ) + visitor?.visitClassReference(classSymbol, getIdentifier(source)) } } } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt index 45bb987b2..601511099 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt @@ -15,9 +15,11 @@ val KEY_SOURCES = CompilerConfigurationKey(VAL_SOURCES) const val VAL_TARGET = "targetroot" val KEY_TARGET = CompilerConfigurationKey(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 = listOf( CliOption( diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt index 289a0cf4e..5833d8883 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt @@ -19,6 +19,7 @@ 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, @@ -26,6 +27,8 @@ class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : Compile ) } + override val pluginId = PLUGIN_ID + override val supportsK2: Boolean get() = true } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt index c2da05fdf..afd51bd0a 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt @@ -10,22 +10,22 @@ class LineMap(private val file: FirFile) { private fun offsetToLineAndCol(offset: Int): Pair? = 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 diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt index 54ea38fa6..e814439c5 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt @@ -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, @@ -45,6 +46,7 @@ class PostAnalysisExtension( } catch (e: Exception) { handleException(e) } + AnalyzerCheckers.visitors.clear() } private fun scipShardPathForFile(file: KtSourceFile): Path? { @@ -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 = @@ -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 diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index f767dda2b..4e904b3ef 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -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 @@ -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 @@ -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) { @@ -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 { @@ -84,6 +88,10 @@ 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() @@ -146,7 +154,7 @@ class ScipTextDocumentBuilder( bodyRenderer = null, propertyAccessorRenderer = null, callArgumentsRenderer = FirCallNoArgumentsRenderer(), - modifierRenderer = FirAllModifierRenderer(), + modifierRenderer = FirAllModifierRenderer(FirModifierRenderer.StaticPolicy.Default), callableSignatureRenderer = FirCallableSignatureRendererForReadability(), declarationRenderer = FirDeclarationRenderer("local "), ) @@ -159,6 +167,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 @@ -184,14 +209,16 @@ 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() } } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt index 80b9866e5..5cfbe6c5a 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt @@ -33,10 +33,10 @@ class ScipVisitor( fun build(): Document = documentBuilder.build() + context(context: CheckerContext) private fun Sequence?.emitAll( element: KtSourceElement, isDefinition: Boolean, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ): List? = this?.onEach { (firBasedSymbol, symbol) -> @@ -45,7 +45,6 @@ class ScipVisitor( symbol, element, isDefinition, - context, enclosingSource, ) } @@ -55,132 +54,138 @@ class ScipVisitor( private fun Sequence.with(firBasedSymbol: FirBasedSymbol<*>?) = this.map { SymbolDescriptorPair(firBasedSymbol, it) } - fun visitPackage(pkg: FqName, element: KtSourceElement, context: CheckerContext) { - cache[pkg].with(null).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitPackage(pkg: FqName, element: KtSourceElement) { + cache[pkg].with(null).emitAll(element, isDefinition = false) } - fun visitClassReference( - firClassSymbol: FirClassLikeSymbol<*>, - element: KtSourceElement, - context: CheckerContext, - ) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitClassReference(firClassSymbol: FirClassLikeSymbol<*>, element: KtSourceElement) { + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false) } - fun visitCallableReference( - firClassSymbol: FirCallableSymbol<*>, - element: KtSourceElement, - context: CheckerContext, - ) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitCallableReference(firClassSymbol: FirCallableSymbol<*>, element: KtSourceElement) { + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false) } + context(context: CheckerContext) fun visitClassOrObject( firClass: FirClassLikeDeclaration, element: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firClass.symbol] .with(firClass.symbol) - .emitAll(element, isDefinition = true, context, enclosingSource) + .emitAll(element, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitPrimaryConstructor( firConstructor: FirConstructor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firConstructor.symbol] .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitSecondaryConstructor( firConstructor: FirConstructor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firConstructor.symbol] .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitNamedFunction( firFunction: FirFunction, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firFunction.symbol] .with(firFunction.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitProperty( firProperty: FirProperty, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firProperty.symbol] .with(firProperty.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitParameter( firParameter: FirValueParameter, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firParameter.symbol] .with(firParameter.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitTypeParameter( firTypeParameter: FirTypeParameter, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firTypeParameter.symbol] .with(firTypeParameter.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitTypeAlias( firTypeAlias: FirTypeAlias, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firTypeAlias.symbol] .with(firTypeAlias.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitPropertyAccessor( firPropertyAccessor: FirPropertyAccessor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firPropertyAccessor.symbol] .with(firPropertyAccessor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) + } + + context(context: CheckerContext) + fun visitEnumEntry( + firEnumEntry: FirEnumEntry, + source: KtSourceElement, + enclosingSource: KtSourceElement? = null, + ) { + cache[firEnumEntry.symbol] + .with(firEnumEntry.symbol) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitSimpleNameExpression( firResolvedNamedReference: FirResolvedNamedReference, source: KtSourceElement, - context: CheckerContext, ) { cache[firResolvedNamedReference.resolvedSymbol] .with(firResolvedNamedReference.resolvedSymbol) - .emitAll(source, isDefinition = false, context) + .emitAll(source, isDefinition = false) } } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index 57f5b1b69..7ba63f961 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -1,20 +1,20 @@ package org.scip_code.scip_java.kotlinc import java.lang.System.err -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember -import org.jetbrains.kotlin.fir.analysis.checkers.getContainingSymbol +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalDeclaredInBlock import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess -import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.utils.memberDeclarationNameOrNull import org.jetbrains.kotlin.fir.packageFqName import org.jetbrains.kotlin.fir.resolve.getContainingDeclaration +import org.jetbrains.kotlin.fir.resolve.getContainingSymbol import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* +import org.jetbrains.kotlin.fir.types.classId +import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly import org.scip_code.scip_java.kotlinc.ScipSymbolDescriptor.Kind import org.scip_code.scip_java.shared.LocalSymbolsCache as SharedLocalSymbolsCache @@ -80,7 +80,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { private fun uncachedSymbol(symbol: FirBasedSymbol<*>?, locals: LocalSymbolsCache): Symbol { if (symbol == null || symbol is FirAnonymousFunctionSymbol) return Symbol.NONE - if (symbol.fir.isLocalMember) return locals + symbol + if (symbol.fir.isLocalDeclaredInBlock) return locals + symbol val owner = getParentSymbol(symbol, locals) @@ -115,10 +115,30 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { return getSymbol(symbol.containingDeclarationSymbol, locals) is FirValueParameterSymbol -> return getSymbol(symbol.containingDeclarationSymbol, locals) + is FirPropertyAccessorSymbol -> return getSymbol(symbol.propertySymbol, locals) is FirCallableSymbol -> { val session = symbol.fir.moduleData.session - return symbol.getContainingSymbol(session)?.let { getSymbol(it, locals) } - ?: getSymbol(symbol.packageFqName()) + val containingSymbol = symbol.getContainingSymbol(session) + // For top-level extension functions/properties (containingSymbol is file or null), + // use the receiver type as a synthetic parent within the package + // (e.g. sample/String#foo().). + if (containingSymbol == null || containingSymbol is FirFileSymbol) { + val receiverClassId = symbol.fir.receiverParameter?.typeRef?.coneType?.classId + if (receiverClassId != null) { + val packageSymbol = getSymbol(symbol.packageFqName()) + return Symbol.createGlobal( + packageSymbol, + ScipSymbolDescriptor( + Kind.TYPE, + receiverClassId.shortClassName.asString(), + ), + ) + } + } + containingSymbol?.let { + return getSymbol(it, locals) + } + return getSymbol(symbol.packageFqName()) } is FirClassLikeSymbol -> { val session = symbol.fir.moduleData.session @@ -142,15 +162,9 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { symbol is FirClassLikeSymbol -> ScipSymbolDescriptor(Kind.TYPE, symbol.classId.shortClassName.asString()) symbol is FirPropertyAccessorSymbol && symbol.isSetter -> - ScipSymbolDescriptor( - Kind.METHOD, - "set" + symbol.propertySymbol.fir.name.toString().capitalizeAsciiOnly(), - ) + ScipSymbolDescriptor(Kind.METHOD, "set") symbol is FirPropertyAccessorSymbol && symbol.isGetter -> - ScipSymbolDescriptor( - Kind.METHOD, - "get" + symbol.propertySymbol.fir.name.toString().capitalizeAsciiOnly(), - ) + ScipSymbolDescriptor(Kind.METHOD, "get") symbol is FirConstructorSymbol -> ScipSymbolDescriptor(Kind.METHOD, "", methodDisambiguator(symbol)) symbol is FirFunctionSymbol -> @@ -164,6 +178,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { symbol is FirValueParameterSymbol -> ScipSymbolDescriptor(Kind.PARAMETER, symbol.name.toString()) symbol is FirVariableSymbol -> ScipSymbolDescriptor(Kind.TERM, symbol.name.toString()) + symbol is FirFileSymbol -> ScipSymbolDescriptor.NONE else -> { err.println("unknown symbol kind ${symbol.javaClass.simpleName}") ScipSymbolDescriptor.NONE @@ -177,14 +192,43 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { val siblings = when (val containingSymbol = symbol.getContainingSymbol(session)) { - is FirClassSymbol -> - (containingSymbol.fir as FirClass).declarations.map { it.symbol } - is FirFileSymbol -> containingSymbol.fir.declarations.map { it.symbol } - null -> - symbol.moduleData.session.symbolProvider.getTopLevelCallableSymbols( - symbol.packageFqName(), - symbol.name, - ) + is FirClassSymbol -> containingSymbol.fir.declarations.map { it.symbol } + is FirFileSymbol, + null -> { + // For top-level extension functions, siblings are the receiver class members + // (if in the same package) followed by other extension functions on the same + // receiver type in this package. This ensures consistent disambiguation + // when both a class member and an extension share the same parent namespace + // (e.g. sample/MyClass#foo(). vs sample/MyClass#foo(+1).). + val receiverClassId = symbol.fir.receiverParameter?.typeRef?.coneType?.classId + if (receiverClassId != null) { + val receiverClass = + session.symbolProvider.getClassLikeSymbolByClassId(receiverClassId) + as? FirClassSymbol<*> + val classMembers = + if (receiverClass?.packageFqName() == symbol.packageFqName()) { + receiverClass.fir.declarations.map { it.symbol } + } else { + emptyList() + } + val extensionFns = + session.symbolProvider + .getTopLevelCallableSymbols(symbol.packageFqName(), symbol.name) + .filter { + it is FirFunctionSymbol<*> && + it.fir.receiverParameter?.typeRef?.coneType?.classId == + receiverClassId + } + classMembers + extensionFns + } else if (containingSymbol is FirFileSymbol) { + containingSymbol.fir.declarations.map { it.symbol } + } else { + session.symbolProvider.getTopLevelCallableSymbols( + symbol.packageFqName(), + symbol.name, + ) + } + } else -> return "()" } @@ -201,7 +245,11 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { } } - if (count == 0 || !found) return "()" + if (!found) { + err.println("methodDisambiguator: ${symbol.callableId} not found in sibling list") + return "()" + } + if (count == 0) return "()" return "(+${count})" } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 36e94ad56..4f77e9a82 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -7,6 +7,7 @@ import io.kotest.assertions.fail import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain import java.io.File import java.nio.file.Path import kotlin.test.Test @@ -15,6 +16,7 @@ import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.io.TempDir import org.scip_code.scip.Document +import org.scip_code.scip.SymbolInformation.Kind import org.scip_code.scip_java.kotlinc.* @OptIn(ExperimentalCompilerApi::class) @@ -107,11 +109,15 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/Banana#" + kind = Kind.Class + enclosingSymbol = "sample/" displayName = "Banana" signatureText = "public final class Banana : Any" }, scipSymbol { symbol = "sample/Banana#foo()." + kind = Kind.Method + enclosingSymbol = "sample/Banana#" displayName = "foo" signatureText = "public final fun foo(): Unit" }, @@ -279,21 +285,29 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/foo()." + kind = Kind.Method + enclosingSymbol = "sample/" displayName = "foo" signatureText = "public final fun foo(): Unit" }, scipSymbol { symbol = "local 0" + kind = Kind.Class + enclosingSymbol = "sample/foo()." displayName = "LocalClass" signatureText = "local final class LocalClass : Any" }, scipSymbol { symbol = "local 1" + kind = Kind.Constructor + enclosingSymbol = "local 0" displayName = "LocalClass" signatureText = "public constructor(): LocalClass" }, scipSymbol { symbol = "local 2" + kind = Kind.Method + enclosingSymbol = "local 0" displayName = "localClassMethod" signatureText = "public final fun localClassMethod(): Unit" }, @@ -302,104 +316,75 @@ class AnalyzerTest { } @Test - fun overrides(@TempDir path: Path) { + fun `lambda parameters`(@TempDir path: Path) { val document = compileScip( path, """ - package sample - - interface Interface { - fun foo() - } + package sample - class Class : Interface { - override fun foo() {} - } - """, + fun use() { + val f = { n: Int -> n * 2 } + } + """, ) val occurrences = arrayOf( - scipOccurrence { - role = REFERENCE - symbol = "sample/" - range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 14 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/Interface#" - range { - startLine = 2 - startCharacter = 10 - endLine = 2 - endCharacter = 19 - } - enclosingRange { - startLine = 2 - endLine = 4 - endCharacter = 1 - } - }, + // val f is a local variable — gets local0 via isLocalMember scipOccurrence { role = DEFINITION - symbol = "sample/Interface#foo()." + symbol = "local 0" range { startLine = 3 startCharacter = 8 endLine = 3 - endCharacter = 11 + endCharacter = 9 } enclosingRange { startLine = 3 startCharacter = 4 endLine = 3 - endCharacter = 13 + endCharacter = 31 } }, + // n is a lambda parameter — gets local1 via the owner == Symbol.NONE path + // (the containing FirAnonymousFunction is skipped, yielding Symbol.NONE as owner) scipOccurrence { role = DEFINITION - symbol = "sample/Class#" + symbol = "local 1" range { - startLine = 6 - startCharacter = 6 - endLine = 6 - endCharacter = 11 + startLine = 3 + startCharacter = 14 + endLine = 3 + endCharacter = 15 } enclosingRange { - startLine = 6 - endLine = 8 - endCharacter = 1 + startLine = 3 + startCharacter = 14 + endLine = 3 + endCharacter = 20 } }, + // explicit type annotation on n emits a class reference scipOccurrence { role = REFERENCE - symbol = "sample/Interface#" + symbol = "kotlin/Int#" range { - startLine = 6 - startCharacter = 14 - endLine = 6 - endCharacter = 23 + startLine = 3 + startCharacter = 17 + endLine = 3 + endCharacter = 20 } }, + // reference to n in the lambda body uses the same local symbol scipOccurrence { - role = DEFINITION - symbol = "sample/Class#foo()." + role = REFERENCE + symbol = "local 1" range { - startLine = 7 - startCharacter = 17 - endLine = 7 - endCharacter = 20 - } - enclosingRange { - startLine = 7 - startCharacter = 4 - endLine = 7 + startLine = 3 + startCharacter = 24 + endLine = 3 endCharacter = 25 } }, @@ -409,211 +394,248 @@ class AnalyzerTest { val symbols = arrayOf( scipSymbol { - symbol = "sample/Interface#" - displayName = "Interface" - signatureText = "public abstract interface Interface : Any" - }, - scipSymbol { - symbol = "sample/Interface#foo()." - displayName = "foo" - signatureText = "public abstract fun foo(): Unit\n" + symbol = "sample/use()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "use" + signatureText = "public final fun use(): Unit" }, scipSymbol { - symbol = "sample/Class#" - displayName = "Class" - signatureText = "public final class Class : Interface" - addOverriddenSymbols("sample/Interface#") + symbol = "local 0" + kind = Kind.Property + enclosingSymbol = "sample/use()." + displayName = "f" + signatureText = "local val f: (Int) -> Int" }, scipSymbol { - symbol = "sample/Class#foo()." - displayName = "foo" - signatureText = "public open override fun foo(): Unit" - addOverriddenSymbols("sample/Interface#foo().") + symbol = "local 1" + kind = Kind.Parameter + displayName = "n" + signatureText = "n: Int" }, ) document.symbolsList.shouldContainAll(*symbols) } @Test - fun `anonymous object`(@TempDir path: Path) { + fun `local functions`(@TempDir path: Path) { val document = compileScip( path, """ - package sample - - interface Interface { - fun foo() - } + package sample - fun main() { - val a = object : Interface { - override fun foo() {} - } - val b = object : Interface { - override fun foo() {} - } - } - """, + fun outer() { + fun inner() {} + fun innerWithReturnType(): Int = 42 + inner() + } + """, ) val occurrences = arrayOf( - scipOccurrence { - role = REFERENCE - symbol = "sample/" - range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 14 - } - }, scipOccurrence { role = DEFINITION - symbol = "sample/Interface#" + symbol = "sample/outer()." range { startLine = 2 - startCharacter = 10 + startCharacter = 4 endLine = 2 - endCharacter = 19 + endCharacter = 9 } enclosingRange { startLine = 2 - endLine = 4 + startCharacter = 0 + endLine = 6 endCharacter = 1 } }, + // inner() — local named function gets a local symbol scipOccurrence { role = DEFINITION - symbol = "sample/Interface#foo()." + symbol = "local 0" range { startLine = 3 startCharacter = 8 endLine = 3 - endCharacter = 11 + endCharacter = 13 } enclosingRange { startLine = 3 startCharacter = 4 endLine = 3 - endCharacter = 13 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/``#" - range { - startLine = 7 - startCharacter = 12 - endLine = 7 endCharacter = 18 } - enclosingRange { - startLine = 7 - startCharacter = 12 - endLine = 9 - endCharacter = 5 - } }, + // innerWithReturnType() — local named function with explicit return type scipOccurrence { role = DEFINITION - symbol = "sample/``#``()." + symbol = "local 1" range { - startLine = 7 - startCharacter = 12 - endLine = 7 - endCharacter = 18 + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 27 } enclosingRange { - startLine = 7 - startCharacter = 12 - endLine = 9 - endCharacter = 5 + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 39 } }, + // Int return-type reference scipOccurrence { role = REFERENCE - symbol = "sample/Interface#" + symbol = "kotlin/Int#" range { - startLine = 7 - startCharacter = 21 - endLine = 7 - endCharacter = 30 + startLine = 4 + startCharacter = 31 + endLine = 4 + endCharacter = 34 } }, + // call site inner() references the same local symbol scipOccurrence { - role = DEFINITION - symbol = "sample/``#foo()." + role = REFERENCE + symbol = "local 0" range { - startLine = 8 - startCharacter = 21 - endLine = 8 - endCharacter = 24 - } - enclosingRange { - startLine = 8 - startCharacter = 8 - endLine = 8 - endCharacter = 29 + startLine = 5 + startCharacter = 4 + endLine = 5 + endCharacter = 9 } }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "local 0" + kind = Kind.Method + enclosingSymbol = "sample/outer()." + displayName = "inner" + signatureText = "local final fun inner(): Unit" + }, + scipSymbol { + symbol = "local 1" + kind = Kind.Method + enclosingSymbol = "sample/outer()." + displayName = "innerWithReturnType" + signatureText = "local final fun innerWithReturnType(): Int" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `user-defined class as return type`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class MyClass + + fun bar(): MyClass = MyClass() + """, + ) + + val occurrences = + arrayOf( scipOccurrence { role = DEFINITION - symbol = "sample/``#" + symbol = "sample/bar()." range { - startLine = 10 - startCharacter = 12 - endLine = 10 - endCharacter = 18 + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 7 } enclosingRange { - startLine = 10 - startCharacter = 12 - endLine = 12 - endCharacter = 5 + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 30 } }, + // MyClass in the return type position generates a class reference scipOccurrence { - role = DEFINITION - symbol = "sample/``#``()." + role = REFERENCE + symbol = "sample/MyClass#" range { - startLine = 10 - startCharacter = 12 - endLine = 10 + startLine = 4 + startCharacter = 11 + endLine = 4 endCharacter = 18 } - enclosingRange { - startLine = 10 - startCharacter = 12 - endLine = 12 - endCharacter = 5 - } }, - scipOccurrence { - role = REFERENCE - symbol = "sample/Interface#" - range { - startLine = 10 - startCharacter = 21 - endLine = 10 - endCharacter = 30 + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/bar()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "bar" + signatureText = "public final fun bar(): MyClass" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `typealias`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + typealias MyAlias = Int + val x: MyAlias = 42 + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MyAlias#" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 17 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 23 } }, + // Note: val x: MyAlias does not emit a REFERENCE for sample/MyAlias# because the + // property checker resolves the type alias to its expansion (Int). scipOccurrence { role = DEFINITION - symbol = "sample/``#foo()." + symbol = "sample/x." range { - startLine = 11 - startCharacter = 21 - endLine = 11 - endCharacter = 24 + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 5 } enclosingRange { - startLine = 11 - startCharacter = 8 - endLine = 11 - endCharacter = 29 + startLine = 3 + startCharacter = 0 + endLine = 3 + endCharacter = 19 } }, ) @@ -622,101 +644,143 @@ class AnalyzerTest { val symbols = arrayOf( scipSymbol { - symbol = "sample/Interface#" - displayName = "Interface" - signatureText = "public abstract interface Interface : Any" - }, - scipSymbol { - symbol = "sample/``#" - displayName = "" - signatureText = "object : Interface" - addOverriddenSymbols("sample/Interface#") - }, - scipSymbol { - symbol = "sample/``#foo()." - displayName = "foo" - signatureText = "public open override fun foo(): Unit" - addOverriddenSymbols("sample/Interface#foo().") - }, - scipSymbol { - symbol = "sample/``#" - displayName = "" - signatureText = "object : Interface" - addOverriddenSymbols("sample/Interface#") - }, - scipSymbol { - symbol = "sample/``#foo()." - displayName = "foo" - signatureText = "public open override fun foo(): Unit" - addOverriddenSymbols("sample/Interface#foo().") - }, + symbol = "sample/MyAlias#" + kind = Kind.TypeAlias + enclosingSymbol = "sample/" + displayName = "MyAlias" + signatureText = "public final typealias MyAlias = Int\n" + } ) document.symbolsList.shouldContainAll(*symbols) } @Test - fun `function return type`(@TempDir path: Path) { + fun `type parameters`(@TempDir path: Path) { val document = compileScip( path, """ - package sample + package sample - fun foo(arg: Int): Boolean = true - """, + fun identity(x: T): T = x + """, ) val occurrences = arrayOf( scipOccurrence { role = DEFINITION - symbol = "sample/foo()." + symbol = "sample/identity()." range { startLine = 2 - startCharacter = 4 + startCharacter = 8 endLine = 2 - endCharacter = 7 + endCharacter = 16 } enclosingRange { startLine = 2 + startCharacter = 0 endLine = 2 - endCharacter = 33 + endCharacter = 29 } }, scipOccurrence { role = DEFINITION - symbol = "sample/foo().(arg)" + symbol = "sample/identity().[T]" range { startLine = 2 - startCharacter = 8 + startCharacter = 5 endLine = 2 - endCharacter = 11 + endCharacter = 6 } enclosingRange { startLine = 2 - startCharacter = 8 + startCharacter = 5 endLine = 2 - endCharacter = 16 + endCharacter = 6 } }, + // Note: T in type-annotation positions (x: T and return type : T) does not produce + // REFERENCE occurrences. The checkers use toClassLikeSymbol() to detect type + // references, which returns null for type parameters, so those usages are not + // currently tracked. + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/identity().[T]" + kind = Kind.TypeParameter + enclosingSymbol = "sample/identity()." + displayName = "T" + signatureText = "T" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `generic class declaration`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class Box(val value: T) { + fun unwrap(): T = value + } + """, + ) + + val occurrences = + arrayOf( scipOccurrence { - role = REFERENCE - symbol = "kotlin/Int#" + role = DEFINITION + symbol = "sample/Box#" range { startLine = 2 - startCharacter = 13 + startCharacter = 6 endLine = 2 - endCharacter = 16 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + endLine = 4 + endCharacter = 1 } }, scipOccurrence { - role = REFERENCE - symbol = "kotlin/Boolean#" + role = DEFINITION + symbol = "sample/Box#[T]" range { startLine = 2 - startCharacter = 19 + startCharacter = 10 endLine = 2 - endCharacter = 26 + endCharacter = 11 + } + enclosingRange { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 11 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Box#unwrap()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 14 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 27 } }, ) @@ -724,18 +788,19 @@ class AnalyzerTest { } @Test - fun `type operators`(@TempDir path: Path) { + fun overrides(@TempDir path: Path) { val document = compileScip( path, """ package sample - fun foo(x: Any) { - when (x) { - is Int -> true - else -> x as Float - } + interface Interface { + fun foo() + } + + class Class : Interface { + override fun foo() {} } """, ) @@ -744,638 +809,2195 @@ class AnalyzerTest { arrayOf( scipOccurrence { role = REFERENCE - symbol = "kotlin/Int#" + symbol = "sample/" range { - startLine = 4 - startCharacter = 11 - endLine = 4 + startLine = 0 + startCharacter = 8 + endLine = 0 endCharacter = 14 } }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Interface#" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 19 + } + enclosingRange { + startLine = 2 + endLine = 4 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Interface#foo()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 11 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 13 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Class#" + range { + startLine = 6 + startCharacter = 6 + endLine = 6 + endCharacter = 11 + } + enclosingRange { + startLine = 6 + endLine = 8 + endCharacter = 1 + } + }, scipOccurrence { role = REFERENCE - symbol = "kotlin/Float#" + symbol = "sample/Interface#" range { - startLine = 5 - startCharacter = 21 - endLine = 5 - endCharacter = 26 + startLine = 6 + startCharacter = 14 + endLine = 6 + endCharacter = 23 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Class#foo()." + range { + startLine = 7 + startCharacter = 17 + endLine = 7 + endCharacter = 20 + } + enclosingRange { + startLine = 7 + startCharacter = 4 + endLine = 7 + endCharacter = 25 } }, ) document.occurrencesList.shouldContainAll(*occurrences) - } - @Test - fun `exception test`(@TempDir path: Path) { - val buildPath = File(path.resolve("build").toString()).apply { mkdir() } - val result = - KotlinCompilation() - .apply { - sources = listOf(SourceFile.testKt("")) - compilerPluginRegistrars = - listOf(AnalyzerRegistrar { throw Exception("sample text") }) - verbose = false - pluginOptions = - listOf( - PluginOption("scip-kotlinc", "sourceroot", path.toString()), - PluginOption("scip-kotlinc", "targetroot", buildPath.toString()), - ) - commandLineProcessors = listOf(AnalyzerCommandLineProcessor()) - workingDir = path.toFile() - } - .compile() - - result.exitCode shouldBe KotlinCompilation.ExitCode.OK + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Interface#" + kind = Kind.Interface + enclosingSymbol = "sample/" + displayName = "Interface" + signatureText = "public abstract interface Interface : Any" + }, + scipSymbol { + symbol = "sample/Interface#foo()." + kind = Kind.Method + enclosingSymbol = "sample/Interface#" + displayName = "foo" + signatureText = "public abstract fun foo(): Unit\n" + }, + scipSymbol { + symbol = "sample/Class#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Class" + signatureText = "public final class Class : Interface" + addOverriddenSymbols("sample/Interface#") + }, + scipSymbol { + symbol = "sample/Class#foo()." + kind = Kind.Method + enclosingSymbol = "sample/Class#" + displayName = "foo" + signatureText = "public open override fun foo(): Unit" + addOverriddenSymbols("sample/Interface#foo().") + }, + ) + document.symbolsList.shouldContainAll(*symbols) } @Test - // shamelessly stolen code snippet from https://learnxinyminutes.com/docs/kotlin/ - fun `learn x in y test`(@TempDir path: Path) { - val buildPath = File(path.resolve("build").toString()).apply { mkdir() } - - val source = - SourceFile.testKt( + fun `anonymous object`(@TempDir path: Path) { + val document = + compileScip( + path, """ - @file:Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER", "NAME_SHADOWING", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE") package sample - fun main(args: Array) { - val fooVal = 10 // we cannot later reassign fooVal to something else - var fooVar = 10 - fooVar = 20 // fooVar can be reassigned - - /* - In most cases, Kotlin can determine what the type of a variable is, - so we don't have to explicitly specify it every time. - We can explicitly declare the type of a variable like so: - */ - val foo: Int = 7 - - /* - Strings can be represented in a similar way as in Java. - Escaping is done with a backslash. - */ - val fooString = "My String Is Here!" - val barString = "Printing on a new line?\nNo Problem!" - val bazString = "Do you want to add a tab?\tNo Problem!" - println(fooString) - println(barString) - println(bazString) - - /* - Strings can contain template expressions. - A template expression starts with a dollar sign (${'$'}). - */ - val fooTemplateString = "$'fooString' has ${"fooString.length"} characters" - println(fooTemplateString) // => My String Is Here! has 18 characters - - /* - For a variable to hold null it must be explicitly specified as nullable. - A variable can be specified as nullable by appending a ? to its type. - We can access a nullable variable by using the ?. operator. - We can use the ?: operator to specify an alternative value to use - if a variable is null. - */ - var fooNullable: String? = "abc" - println(fooNullable?.length) // => 3 - println(fooNullable?.length ?: -1) // => 3 - fooNullable = null - println(fooNullable?.length) // => null - println(fooNullable?.length ?: -1) // => -1 - - /* - Functions can be declared using the "fun" keyword. - Function arguments are specified in brackets after the function name. - Function arguments can optionally have a default value. - The function return type, if required, is specified after the arguments. - */ - fun hello(name: String = "world"): String { - return "Hello, $'name'!" - } - println(hello("foo")) // => Hello, foo! - println(hello(name = "bar")) // => Hello, bar! - println(hello()) // => Hello, world! - - /* - A function parameter may be marked with the "vararg" keyword - to allow a variable number of arguments to be passed to the function. - */ - fun varargExample(vararg names: Int) { - println("Argument has ${"names.size"} elements") - } - varargExample() // => Argument has 0 elements - varargExample(1) // => Argument has 1 elements - varargExample(1, 2, 3) // => Argument has 3 elements - - /* - When a function consists of a single expression then the curly brackets can - be omitted. The body is specified after the = symbol. - */ - fun odd(x: Int): Boolean = x % 2 == 1 - println(odd(6)) // => false - println(odd(7)) // => true - - // If the return type can be inferred then we don't need to specify it. - fun even(x: Int) = x % 2 == 0 - println(even(6)) // => true - println(even(7)) // => false + interface Interface { + fun foo() + } - // Functions can take functions as arguments and return functions. - fun not(f: (Int) -> Boolean): (Int) -> Boolean { - return {n -> !f.invoke(n)} + fun main() { + val a = object : Interface { + override fun foo() {} } - // Named functions can be specified as arguments using the :: operator. - val notOdd = not(::odd) - val notEven = not(::even) - // Lambda expressions can be specified as arguments. - val notZero = not {n -> n == 0} - /* - If a lambda has only one parameter - then its declaration can be omitted (along with the ->). - The name of the single parameter will be "it". - */ - val notPositive = not {it > 0} - for (i in 0..4) { - println("${"notOdd(i)"} ${"notEven(i)"} ${"notZero(i)"} ${"notPositive(i)"}") + val b = object : Interface { + override fun foo() {} } + } + """, + ) - // The "class" keyword is used to declare classes. - class ExampleClass(val x: Int) { - fun memberFunction(y: Int): Int { - return x + y + val occurrences = + arrayOf( + scipOccurrence { + role = REFERENCE + symbol = "sample/" + range { + startLine = 0 + startCharacter = 8 + endLine = 0 + endCharacter = 14 } - - infix fun infixMemberFunction(y: Int): Int { - return x * y + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Interface#" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 19 } - } - /* - To create a new instance we call the constructor. - Note that Kotlin does not have a "new" keyword. - */ - val fooExampleClass = ExampleClass(7) - // Member functions can be called using dot notation. - println(fooExampleClass.memberFunction(4)) // => 11 - /* - If a function has been marked with the "infix" keyword then it can be - called using infix notation. - */ - println(fooExampleClass infixMemberFunction 4) // => 28 - - /* - Data classes are a concise way to create classes that just hold data. - The "hashCode"/"equals" and "toString" methods are automatically generated. - */ - data class DataClassExample (val x: Int, val y: Int, val z: Int) - val fooData = DataClassExample(1, 2, 4) - println(fooData) // => DataClassExample(x=1, y=2, z=4) - - // Data classes have a "copy" function. - val fooCopy = fooData.copy(y = 100) - println(fooCopy) // => DataClassExample(x=1, y=100, z=4) - - // Objects can be destructured into multiple variables. - val (a, b, c) = fooCopy - println("$'a' $'b' $'c'") // => 1 100 4 - - // destructuring in "for" loop - for ((a, b, c) in listOf(fooData)) { - println("$'a' $'b' $'c'") // => 1 2 4 - } - - val mapData = mapOf("a" to 1, "b" to 2) - // Map.Entry is destructurable as well - for ((key, value) in mapData) { - println("$'key' -> $'value'") - } - - // The "with" function is similar to the JavaScript "with" statement. - data class MutableDataClassExample (var x: Int, var y: Int, var z: Int) - val fooMutableData = MutableDataClassExample(7, 4, 9) - with (fooMutableData) { - x -= 2 - y += 2 - z-- - } - println(fooMutableData) // => MutableDataClassExample(x=5, y=6, z=8) - - /* - We can create a list using the "listOf" function. - The list will be immutable - elements cannot be added or removed. - */ - val fooList = listOf("a", "b", "c") - println(fooList.size) // => 3 - println(fooList.first()) // => a - println(fooList.last()) // => c - // Elements of a list can be accessed by their index. - println(fooList[1]) // => b - - // A mutable list can be created using the "mutableListOf" function. - val fooMutableList = mutableListOf("a", "b", "c") - fooMutableList.add("d") - println(fooMutableList.last()) // => d - println(fooMutableList.size) // => 4 - - // We can create a set using the "setOf" function. - val fooSet = setOf("a", "b", "c") + enclosingRange { + startLine = 2 + endLine = 4 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Interface#foo()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 11 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 13 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 1" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 18 + } + enclosingRange { + startLine = 7 + startCharacter = 12 + endLine = 9 + endCharacter = 5 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 2" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 18 + } + enclosingRange { + startLine = 7 + startCharacter = 12 + endLine = 9 + endCharacter = 5 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Interface#" + range { + startLine = 7 + startCharacter = 21 + endLine = 7 + endCharacter = 30 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 3" + range { + startLine = 8 + startCharacter = 21 + endLine = 8 + endCharacter = 24 + } + enclosingRange { + startLine = 8 + startCharacter = 8 + endLine = 8 + endCharacter = 29 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 5" + range { + startLine = 10 + startCharacter = 12 + endLine = 10 + endCharacter = 18 + } + enclosingRange { + startLine = 10 + startCharacter = 12 + endLine = 12 + endCharacter = 5 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 6" + range { + startLine = 10 + startCharacter = 12 + endLine = 10 + endCharacter = 18 + } + enclosingRange { + startLine = 10 + startCharacter = 12 + endLine = 12 + endCharacter = 5 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Interface#" + range { + startLine = 10 + startCharacter = 21 + endLine = 10 + endCharacter = 30 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "local 7" + range { + startLine = 11 + startCharacter = 21 + endLine = 11 + endCharacter = 24 + } + enclosingRange { + startLine = 11 + startCharacter = 8 + endLine = 11 + endCharacter = 29 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Interface#" + kind = Kind.Interface + enclosingSymbol = "sample/" + displayName = "Interface" + signatureText = "public abstract interface Interface : Any" + }, + scipSymbol { + symbol = "local 1" + kind = Kind.Class + enclosingSymbol = "local 0" + displayName = "" + signatureText = "object : Interface" + addOverriddenSymbols("sample/Interface#") + }, + scipSymbol { + symbol = "local 3" + kind = Kind.Method + enclosingSymbol = "local 1" + displayName = "foo" + signatureText = "public open override fun foo(): Unit" + addOverriddenSymbols("sample/Interface#foo().") + }, + scipSymbol { + symbol = "local 5" + kind = Kind.Class + enclosingSymbol = "local 4" + displayName = "" + signatureText = "object : Interface" + addOverriddenSymbols("sample/Interface#") + }, + scipSymbol { + symbol = "local 7" + kind = Kind.Method + enclosingSymbol = "local 5" + displayName = "foo" + signatureText = "public open override fun foo(): Unit" + addOverriddenSymbols("sample/Interface#foo().") + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `function return type`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun foo(arg: Int): Boolean = true + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/foo()." + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 7 + } + enclosingRange { + startLine = 2 + endLine = 2 + endCharacter = 33 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/foo().(arg)" + range { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 11 + } + enclosingRange { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 16 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 2 + startCharacter = 13 + endLine = 2 + endCharacter = 16 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Boolean#" + range { + startLine = 2 + startCharacter = 19 + endLine = 2 + endCharacter = 26 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + } + + @Test + fun `type operators`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun foo(x: Any) { + when (x) { + is Int -> true + else -> x as Float + } + } + + class Wrapper + fun classify(x: Any) { + if (x is Wrapper) {} + val s = x as? String + val w = x as Wrapper + } + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 4 + startCharacter = 11 + endLine = 4 + endCharacter = 14 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Float#" + range { + startLine = 5 + startCharacter = 21 + endLine = 5 + endCharacter = 26 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Wrapper#" + range { + startLine = 11 + startCharacter = 13 + endLine = 11 + endCharacter = 20 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 12 + startCharacter = 18 + endLine = 12 + endCharacter = 24 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Wrapper#" + range { + startLine = 13 + startCharacter = 17 + endLine = 13 + endCharacter = 24 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + } + + @Test + fun `exception test`(@TempDir path: Path) { + val buildPath = File(path.resolve("build").toString()).apply { mkdir() } + val result = + KotlinCompilation() + .apply { + sources = listOf(SourceFile.testKt("")) + compilerPluginRegistrars = + listOf(AnalyzerRegistrar { throw Exception("sample text") }) + verbose = false + messageOutputStream = java.io.OutputStream.nullOutputStream() + pluginOptions = + listOf( + PluginOption("scip-kotlinc", "sourceroot", path.toString()), + PluginOption("scip-kotlinc", "targetroot", buildPath.toString()), + ) + commandLineProcessors = listOf(AnalyzerCommandLineProcessor()) + workingDir = path.toFile() + } + .compile() + + result.exitCode shouldBe KotlinCompilation.ExitCode.OK + result.messages shouldContain "Exception in scip-kotlin compiler plugin:" + result.messages shouldContain "sample text" + } + + @Test + // shamelessly stolen code snippet from https://learnxinyminutes.com/docs/kotlin/ + fun `learn x in y test`(@TempDir path: Path) { + val buildPath = File(path.resolve("build").toString()).apply { mkdir() } + + val source = + SourceFile.testKt( + """ + @file:Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER", "NAME_SHADOWING", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE") + package sample + + fun main(args: Array) { + val fooVal = 10 // we cannot later reassign fooVal to something else + var fooVar = 10 + fooVar = 20 // fooVar can be reassigned + + /* + In most cases, Kotlin can determine what the type of a variable is, + so we don't have to explicitly specify it every time. + We can explicitly declare the type of a variable like so: + */ + val foo: Int = 7 + + /* + Strings can be represented in a similar way as in Java. + Escaping is done with a backslash. + */ + val fooString = "My String Is Here!" + val barString = "Printing on a new line?\nNo Problem!" + val bazString = "Do you want to add a tab?\tNo Problem!" + println(fooString) + println(barString) + println(bazString) + + /* + Strings can contain template expressions. + A template expression starts with a dollar sign (${'$'}). + */ + val fooTemplateString = "$'fooString' has ${"fooString.length"} characters" + println(fooTemplateString) // => My String Is Here! has 18 characters + + /* + For a variable to hold null it must be explicitly specified as nullable. + A variable can be specified as nullable by appending a ? to its type. + We can access a nullable variable by using the ?. operator. + We can use the ?: operator to specify an alternative value to use + if a variable is null. + */ + var fooNullable: String? = "abc" + println(fooNullable?.length) // => 3 + println(fooNullable?.length ?: -1) // => 3 + fooNullable = null + println(fooNullable?.length) // => null + println(fooNullable?.length ?: -1) // => -1 + + /* + Functions can be declared using the "fun" keyword. + Function arguments are specified in brackets after the function name. + Function arguments can optionally have a default value. + The function return type, if required, is specified after the arguments. + */ + fun hello(name: String = "world"): String { + return "Hello, $'name'!" + } + println(hello("foo")) // => Hello, foo! + println(hello(name = "bar")) // => Hello, bar! + println(hello()) // => Hello, world! + + /* + A function parameter may be marked with the "vararg" keyword + to allow a variable number of arguments to be passed to the function. + */ + fun varargExample(vararg names: Int) { + println("Argument has ${"names.size"} elements") + } + varargExample() // => Argument has 0 elements + varargExample(1) // => Argument has 1 elements + varargExample(1, 2, 3) // => Argument has 3 elements + + /* + When a function consists of a single expression then the curly brackets can + be omitted. The body is specified after the = symbol. + */ + fun odd(x: Int): Boolean = x % 2 == 1 + println(odd(6)) // => false + println(odd(7)) // => true + + // If the return type can be inferred then we don't need to specify it. + fun even(x: Int) = x % 2 == 0 + println(even(6)) // => true + println(even(7)) // => false + + // Functions can take functions as arguments and return functions. + fun not(f: (Int) -> Boolean): (Int) -> Boolean { + return {n -> !f.invoke(n)} + } + // Named functions can be specified as arguments using the :: operator. + val notOdd = not(::odd) + val notEven = not(::even) + // Lambda expressions can be specified as arguments. + val notZero = not {n -> n == 0} + /* + If a lambda has only one parameter + then its declaration can be omitted (along with the ->). + The name of the single parameter will be "it". + */ + val notPositive = not {it > 0} + for (i in 0..4) { + println("${"notOdd(i)"} ${"notEven(i)"} ${"notZero(i)"} ${"notPositive(i)"}") + } + + // The "class" keyword is used to declare classes. + class ExampleClass(val x: Int) { + fun memberFunction(y: Int): Int { + return x + y + } + + infix fun infixMemberFunction(y: Int): Int { + return x * y + } + } + /* + To create a new instance we call the constructor. + Note that Kotlin does not have a "new" keyword. + */ + val fooExampleClass = ExampleClass(7) + // Member functions can be called using dot notation. + println(fooExampleClass.memberFunction(4)) // => 11 + /* + If a function has been marked with the "infix" keyword then it can be + called using infix notation. + */ + println(fooExampleClass infixMemberFunction 4) // => 28 + + /* + Data classes are a concise way to create classes that just hold data. + The "hashCode"/"equals" and "toString" methods are automatically generated. + */ + data class DataClassExample (val x: Int, val y: Int, val z: Int) + val fooData = DataClassExample(1, 2, 4) + println(fooData) // => DataClassExample(x=1, y=2, z=4) + + // Data classes have a "copy" function. + val fooCopy = fooData.copy(y = 100) + println(fooCopy) // => DataClassExample(x=1, y=100, z=4) + + // Objects can be destructured into multiple variables. + val (a, b, c) = fooCopy + println("$'a' $'b' $'c'") // => 1 100 4 + + // destructuring in "for" loop + for ((a, b, c) in listOf(fooData)) { + println("$'a' $'b' $'c'") // => 1 2 4 + } + + val mapData = mapOf("a" to 1, "b" to 2) + // Map.Entry is destructurable as well + for ((key, value) in mapData) { + println("$'key' -> $'value'") + } + + // The "with" function is similar to the JavaScript "with" statement. + data class MutableDataClassExample (var x: Int, var y: Int, var z: Int) + val fooMutableData = MutableDataClassExample(7, 4, 9) + with (fooMutableData) { + x -= 2 + y += 2 + z-- + } + println(fooMutableData) // => MutableDataClassExample(x=5, y=6, z=8) + + /* + We can create a list using the "listOf" function. + The list will be immutable - elements cannot be added or removed. + */ + val fooList = listOf("a", "b", "c") + println(fooList.size) // => 3 + println(fooList.first()) // => a + println(fooList.last()) // => c + // Elements of a list can be accessed by their index. + println(fooList[1]) // => b + + // A mutable list can be created using the "mutableListOf" function. + val fooMutableList = mutableListOf("a", "b", "c") + fooMutableList.add("d") + println(fooMutableList.last()) // => d + println(fooMutableList.size) // => 4 + + // We can create a set using the "setOf" function. + val fooSet = setOf("a", "b", "c") println(fooSet.contains("a")) // => true println(fooSet.contains("z")) // => false - // We can create a map using the "mapOf" function. - val fooMap = mapOf("a" to 8, "b" to 7, "c" to 9) - // Map values can be accessed by their key. - println(fooMap["a"]) // => 8 + // We can create a map using the "mapOf" function. + val fooMap = mapOf("a" to 8, "b" to 7, "c" to 9) + // Map values can be accessed by their key. + println(fooMap["a"]) // => 8 + + /* + Sequences represent lazily-evaluated collections. + We can create a sequence using the "generateSequence" function. + */ + val fooSequence = generateSequence(1, { it + 1 }) + val x = fooSequence.take(10).toList() + println(x) // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + // An example of using a sequence to generate Fibonacci numbers: + fun fibonacciSequence(): Sequence { + var a = 0L + var b = 1L + + fun next(): Long { + val result = a + b + a = b + b = result + return a + } + + return generateSequence(::next) + } + val y = fibonacciSequence().take(10).toList() + println(y) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + + // Kotlin provides higher-order functions for working with collections. + val z = (1..9).map {it * 3} + .filter {it < 20} + .groupBy {it % 2 == 0} + .mapKeys {if (it.key) "even" else "odd"} + println(z) // => {odd=[3, 9, 15], even=[6, 12, 18]} + + // A "for" loop can be used with anything that provides an iterator. + for (c in "hello") { + println(c) + } + + // "while" loops work in the same way as other languages. + var ctr = 0 + while (ctr < 5) { + println(ctr) + ctr++ + } + do { + println(ctr) + ctr++ + } while (ctr < 10) + + /* + "if" can be used as an expression that returns a value. + For this reason the ternary ?: operator is not needed in Kotlin. + */ + val num = 5 + val message = if (num % 2 == 0) "even" else "odd" + println("$'num' is $'message'") // => 5 is odd + + // "when" can be used as an alternative to "if-else if" chains. + val i = 10 + when { + i < 7 -> println("first block") + fooString.startsWith("hello") -> println("second block") + else -> println("else block") + } + + // "when" can be used with an argument. + when (i) { + 0, 21 -> println("0 or 21") + in 1..20 -> println("in the range 1 to 20") + else -> println("none of the above") + } + + // "when" can be used as a function that returns a value. + var result = when (i) { + 0, 21 -> "0 or 21" + in 1..20 -> "in the range 1 to 20" + else -> "none of the above" + } + println(result) + + /* + We can check if an object is of a particular type by using the "is" operator. + If an object passes a type check then it can be used as that type without + explicitly casting it. + */ + fun smartCastExample(x: Any) : Boolean { + if (x is Boolean) { + // x is automatically cast to Boolean + return x + } else if (x is Int) { + // x is automatically cast to Int + return x > 0 + } else if (x is String) { + // x is automatically cast to String + return x.isNotEmpty() + } else { + return false + } + } + println(smartCastExample("Hello, world!")) // => true + println(smartCastExample("")) // => false + println(smartCastExample(5)) // => true + println(smartCastExample(0)) // => false + println(smartCastExample(true)) // => true + + // Smartcast also works with when block + fun smartCastWhenExample(x: Any) = when (x) { + is Boolean -> x + is Int -> x > 0 + is String -> x.isNotEmpty() + else -> false + } + + /* + Extensions are a way to add new functionality to a class. + This is similar to C# extension methods. + */ + fun String.remove(c: Char): String { + return this.filter {it != c} + } + println("Hello, world!".remove('l')) // => Heo, word! + } + + // Enum classes are similar to Java enum types. + enum class EnumExample { + A, B, C // Enum constants are separated with commas. + } + fun printEnum() = println(EnumExample.A) // => A + + // Since each enum is an instance of the enum class, they can be initialized as: + enum class EnumExample1(val value: Int) { + A(value = 1), + B(value = 2), + C(value = 3) + } + fun printProperty() = println(EnumExample1.A.value) // => 1 + + // Every enum has properties to obtain its name and ordinal(position) in the enum class declaration: + fun printName() = println(EnumExample1.A.name) // => A + fun printPosition() = println(EnumExample1.A.ordinal) // => 0 + + /* + The "object" keyword can be used to create singleton objects. + We cannot instantiate it but we can refer to its unique instance by its name. + This is similar to Scala singleton objects. + */ + object ObjectExample { + fun hello(): String { + return "hello" + } + + override fun toString(): String { + return "Hello, it's me, ${"ObjectExample::class.simpleName"}" + } + } + + + fun useSingletonObject() { + println(ObjectExample.hello()) // => hello + // In Kotlin, "Any" is the root of the class hierarchy, just like "Object" is in Java + val someRef: Any = ObjectExample + println(someRef) // => Hello, it's me, ObjectExample + } + + + /* The not-null assertion operator (!!) converts any value to a non-null type and + throws an exception if the value is null. + */ + var b: String? = "abc" + val l = b!!.length + + data class Counter(var value: Int) { + // overload Counter += Int + operator fun plusAssign(increment: Int) { + this.value += increment + } + + // overload Counter++ and ++Counter + operator fun inc() = Counter(value + 1) + + // overload Counter + Counter + operator fun plus(other: Counter) = Counter(this.value + other.value) + + // overload Counter * Counter + operator fun times(other: Counter) = Counter(this.value * other.value) + + // overload Counter * Int + operator fun times(value: Int) = Counter(this.value * value) + + // overload Counter in Counter + operator fun contains(other: Counter) = other.value == this.value + + // overload Counter[Int] = Int + operator fun set(index: Int, value: Int) { + this.value = index + value + } + + // overload Counter instance invocation + operator fun invoke() = println("The value of the counter is $'value'") + + } + /* You can also overload operators through extension methods */ + // overload -Counter + operator fun Counter.unaryMinus() = Counter(-this.value) + + fun operatorOverloadingDemo() { + var counter1 = Counter(0) + var counter2 = Counter(5) + counter1 += 7 + println(counter1) // => Counter(value=7) + println(counter1 + counter2) // => Counter(value=12) + println(counter1 * counter2) // => Counter(value=35) + println(counter2 * 2) // => Counter(value=10) + println(counter1 in Counter(5)) // => false + println(counter1 in Counter(7)) // => true + counter1[26] = 10 + println(counter1) // => Counter(value=36) + counter1() // => The value of the counter is 36 + println(-counter2) // => Counter(value=-5) + } + """ + ) + + val result = + KotlinCompilation() + .apply { + sources = listOf(source) + compilerPluginRegistrars = listOf(AnalyzerRegistrar()) + verbose = false + pluginOptions = + listOf( + PluginOption("scip-kotlinc", "sourceroot", path.toString()), + PluginOption("scip-kotlinc", "targetroot", buildPath.toString()), + ) + commandLineProcessors = listOf(AnalyzerCommandLineProcessor()) + workingDir = path.toFile() + } + .compile() + + result.exitCode shouldBe KotlinCompilation.ExitCode.OK + } + + @Test + fun `compound package name semicolon test`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package hello.sample; + class Apple + """ + .trimIndent(), + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = REFERENCE + symbol = "hello/" + range { + startLine = 0 + startCharacter = 8 + endLine = 0 + endCharacter = 13 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "hello/sample/" + range { + startLine = 0 + startCharacter = 14 + endLine = 0 + endCharacter = 20 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "hello/sample/Apple#" + range { + startLine = 1 + startCharacter = 6 + endLine = 1 + endCharacter = 11 + } + enclosingRange { + startLine = 1 + endLine = 1 + endCharacter = 11 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "hello/sample/Apple#``()." + range { + startLine = 1 + startCharacter = 6 + endLine = 1 + endCharacter = 11 + } + enclosingRange { + startLine = 1 + endLine = 1 + endCharacter = 11 + } + }, + ) + + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "hello/sample/Apple#" + kind = Kind.Class + enclosingSymbol = "hello/sample/" + displayName = "Apple" + signatureText = "public final class Apple : Any" + } + ) + + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `simple package name semicolon test`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample; + class Banana { + fun foo() { } + }""", + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = REFERENCE + symbol = "sample/" + range { + startLine = 0 + startCharacter = 8 + endLine = 0 + endCharacter = 14 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Banana#" + range { + startLine = 1 + startCharacter = 6 + endLine = 1 + endCharacter = 12 + } + enclosingRange { + startLine = 1 + endLine = 3 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Banana#foo()." + range { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 11 + } + enclosingRange { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 17 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Banana#" + range { + startLine = 1 + startCharacter = 6 + endLine = 1 + endCharacter = 12 + } + enclosingRange { + startLine = 1 + endLine = 3 + endCharacter = 1 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Banana#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Banana" + signatureText = "public final class Banana : Any" + }, + scipSymbol { + symbol = "sample/Banana#foo()." + kind = Kind.Method + enclosingSymbol = "sample/Banana#" + displayName = "foo" + signatureText = "public final fun foo(): Unit" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } - /* - Sequences represent lazily-evaluated collections. - We can create a sequence using the "generateSequence" function. - */ - val fooSequence = generateSequence(1, { it + 1 }) - val x = fooSequence.take(10).toList() - println(x) // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + @Test + fun documentation(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + import java.io.Serializable + abstract class DocstringSuperclass - // An example of using a sequence to generate Fibonacci numbers: - fun fibonacciSequence(): Sequence { - var a = 0L - var b = 1L + /** Example class docstring */ + class Docstrings: DocstringSuperclass(), Serializable - fun next(): Long { - val result = a + b - a = b - b = result - return a - } + /** + * Example method docstring + * + **/ + fun docstrings(msg: String): Int { return msg.length } + """ + .trimIndent(), + ) + document.assertDocumentation("sample/Docstrings#", "Example class docstring") + document.assertDocumentation("sample/docstrings().", "Example method docstring") + } - return generateSequence(::next) - } - val y = fibonacciSequence().take(10).toList() - println(y) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + @Test + fun `extension receiver type reference`(@TempDir path: Path) { + // String is from the kotlin package; our extension in sample gets + // symbol sample/String#foo(). — distinct from kotlin/String#foo(). + // This means extensions on cross-package types never collide with + // the receiver class's own methods in the symbol table. + val document = + compileScip( + path, + """ + package sample - // Kotlin provides higher-order functions for working with collections. - val z = (1..9).map {it * 3} - .filter {it < 20} - .groupBy {it % 2 == 0} - .mapKeys {if (it.key) "even" else "odd"} - println(z) // => {odd=[3, 9, 15], even=[6, 12, 18]} + fun String.foo(): Int = 42 + fun use(s: String) = s.foo() + """, + ) - // A "for" loop can be used with anything that provides an iterator. - for (c in "hello") { - println(c) - } + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/String#foo()." + range { + startLine = 2 + startCharacter = 11 + endLine = 2 + endCharacter = 14 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 26 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 10 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 2 + startCharacter = 18 + endLine = 2 + endCharacter = 21 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/String#foo()." + range { + startLine = 3 + startCharacter = 23 + endLine = 3 + endCharacter = 26 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - // "while" loops work in the same way as other languages. - var ctr = 0 - while (ctr < 5) { - println(ctr) - ctr++ + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/String#foo()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "foo" + signatureText = "public final fun String.foo(): Int" } - do { - println(ctr) - ctr++ - } while (ctr < 10) + ) + document.symbolsList.shouldContainAll(*symbols) + } - /* - "if" can be used as an expression that returns a value. - For this reason the ternary ?: operator is not needed in Kotlin. - */ - val num = 5 - val message = if (num % 2 == 0) "even" else "odd" - println("$'num' is $'message'") // => 5 is odd + @Test + fun `extension property receiver type reference`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample - // "when" can be used as an alternative to "if-else if" chains. - val i = 10 - when { - i < 7 -> println("first block") - fooString.startsWith("hello") -> println("second block") - else -> println("else block") - } + val Int.asString: String get() = this.toString() + fun use() = 42.asString + """, + ) - // "when" can be used with an argument. - when (i) { - 0, 21 -> println("0 or 21") - in 1..20 -> println("in the range 1 to 20") - else -> println("none of the above") - } + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Int#asString." + range { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 16 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 48 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 7 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 2 + startCharacter = 18 + endLine = 2 + endCharacter = 24 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Int#asString." + range { + startLine = 3 + startCharacter = 15 + endLine = 3 + endCharacter = 23 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - // "when" can be used as a function that returns a value. - var result = when (i) { - 0, 21 -> "0 or 21" - in 1..20 -> "in the range 1 to 20" - else -> "none of the above" + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Int#asString." + kind = Kind.Property + enclosingSymbol = "sample/" + displayName = "asString" + signatureText = "public final val Int.asString: String" } - println(result) + ) + document.symbolsList.shouldContainAll(*symbols) + } - /* - We can check if an object is of a particular type by using the "is" operator. - If an object passes a type check then it can be used as that type without - explicitly casting it. - */ - fun smartCastExample(x: Any) : Boolean { - if (x is Boolean) { - // x is automatically cast to Boolean - return x - } else if (x is Int) { - // x is automatically cast to Int - return x > 0 - } else if (x is String) { - // x is automatically cast to String - return x.isNotEmpty() - } else { - return false + @Test + fun `extension overload disambiguator`(@TempDir path: Path) { + // When a class already has a member named foo() and an extension also + // adds foo(), the extension is counted after the member in the combined + // sibling list (class members + same-package extensions on the same + // receiver type), so the extension gets the (+1) disambiguator and the + // two produce distinct symbols. + val document = + compileScip( + path, + """ + package sample + + class MyClass { + fun foo() {} } - } - println(smartCastExample("Hello, world!")) // => true - println(smartCastExample("")) // => false - println(smartCastExample(5)) // => true - println(smartCastExample(0)) // => false - println(smartCastExample(true)) // => true + fun MyClass.foo(x: Int) {} + """, + ) - // Smartcast also works with when block - fun smartCastWhenExample(x: Any) = when (x) { - is Boolean -> x - is Int -> x > 0 - is String -> x.isNotEmpty() - else -> false - } + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MyClass#foo()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 11 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 16 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/MyClass#foo(+1)." + range { + startLine = 5 + startCharacter = 12 + endLine = 5 + endCharacter = 15 + } + enclosingRange { + startLine = 5 + startCharacter = 0 + endLine = 5 + endCharacter = 26 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + } - /* - Extensions are a way to add new functionality to a class. - This is similar to C# extension methods. - */ - fun String.remove(c: Char): String { - return this.filter {it != c} - } - println("Hello, world!".remove('l')) // => Heo, word! - } + @Test + fun `enum entry definitions`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample - // Enum classes are similar to Java enum types. - enum class EnumExample { - A, B, C // Enum constants are separated with commas. - } - fun printEnum() = println(EnumExample.A) // => A + enum class Color { RED, GREEN, BLUE } - // Since each enum is an instance of the enum class, they can be initialized as: - enum class EnumExample1(val value: Int) { - A(value = 1), - B(value = 2), - C(value = 3) - } - fun printProperty() = println(EnumExample1.A.value) // => 1 + fun useEnum(): Color = Color.RED + """, + ) - // Every enum has properties to obtain its name and ordinal(position) in the enum class declaration: - fun printName() = println(EnumExample1.A.name) // => A - fun printPosition() = println(EnumExample1.A.ordinal) // => 0 + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#" + range { + startLine = 2 + startCharacter = 11 + endLine = 2 + endCharacter = 16 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 37 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#RED." + range { + startLine = 2 + startCharacter = 19 + endLine = 2 + endCharacter = 22 + } + enclosingRange { + startLine = 2 + startCharacter = 19 + endLine = 2 + endCharacter = 23 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#GREEN." + range { + startLine = 2 + startCharacter = 24 + endLine = 2 + endCharacter = 29 + } + enclosingRange { + startLine = 2 + startCharacter = 24 + endLine = 2 + endCharacter = 30 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#BLUE." + range { + startLine = 2 + startCharacter = 31 + endLine = 2 + endCharacter = 35 + } + enclosingRange { + startLine = 2 + startCharacter = 31 + endLine = 2 + endCharacter = 35 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Color#" + range { + startLine = 4 + startCharacter = 23 + endLine = 4 + endCharacter = 28 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Color#RED." + range { + startLine = 4 + startCharacter = 29 + endLine = 4 + endCharacter = 32 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - /* - The "object" keyword can be used to create singleton objects. - We cannot instantiate it but we can refer to its unique instance by its name. - This is similar to Scala singleton objects. - */ - object ObjectExample { - fun hello(): String { - return "hello" - } + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Color#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Color" + addOverriddenSymbols("kotlin/Enum#") + signatureText = "public final enum class Color : Enum" + }, + scipSymbol { + symbol = "sample/Color#RED." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "RED" + signatureText = "public final static enum entry RED: Color" + }, + scipSymbol { + symbol = "sample/Color#GREEN." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "GREEN" + signatureText = "public final static enum entry GREEN: Color" + }, + scipSymbol { + symbol = "sample/Color#BLUE." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "BLUE" + signatureText = "public final static enum entry BLUE: Color" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } - override fun toString(): String { - return "Hello, it's me, ${"ObjectExample::class.simpleName"}" - } - } + @Test + fun `enum entry with body`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + enum class Op { + PLUS { + override fun apply(a: Int, b: Int) = a + b + }; - fun useSingletonObject() { - println(ObjectExample.hello()) // => hello - // In Kotlin, "Any" is the root of the class hierarchy, just like "Object" is in Java - val someRef: Any = ObjectExample - println(someRef) // => Hello, it's me, ObjectExample - } + abstract fun apply(a: Int, b: Int): Int + } + """, + ) + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Op#PLUS." + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 8 + } + // Body-having enum entry: enclosing_range covers PLUS through the + // trailing `};` that terminates the entry list. + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 6 + } + }, + // An enum entry with a body is modeled as a synthetic anonymous subclass, + // so its overridden member is a local symbol (local2), not a global + // sample/Op#PLUS.apply(). It still gets an enclosing_range spanning the + // function declaration. + scipOccurrence { + role = DEFINITION + symbol = "local 2" + range { + startLine = 4 + startCharacter = 21 + endLine = 4 + endCharacter = 26 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 50 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - /* The not-null assertion operator (!!) converts any value to a non-null type and - throws an exception if the value is null. - */ - var b: String? = "abc" - val l = b!!.length + val symbols = + arrayOf( + // The entry body becomes an anonymous class enclosed by the entry... + scipSymbol { + symbol = "local 0" + kind = Kind.Class + enclosingSymbol = "sample/Op#PLUS." + displayName = "" + addOverriddenSymbols("sample/Op#") + signatureText = "object : Op" + }, + // ...and the override is a method of that anonymous class. + scipSymbol { + symbol = "local 2" + kind = Kind.Method + enclosingSymbol = "local 0" + displayName = "apply" + addOverriddenSymbols("sample/Op#apply().") + signatureText = "public open override fun apply(a: Int, b: Int): Int" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } - data class Counter(var value: Int) { - // overload Counter += Int - operator fun plusAssign(increment: Int) { - this.value += increment - } + @Test + fun `named object declarations`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample - // overload Counter++ and ++Counter - operator fun inc() = Counter(value + 1) + object MySingleton { + fun hello(): String = "hi" + } + fun use() = MySingleton.hello() + """, + ) - // overload Counter + Counter - operator fun plus(other: Counter) = Counter(this.value + other.value) + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MySingleton#" + range { + startLine = 2 + startCharacter = 7 + endLine = 2 + endCharacter = 18 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 4 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/MySingleton#hello()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 13 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 30 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/MySingleton#" + range { + startLine = 5 + startCharacter = 12 + endLine = 5 + endCharacter = 23 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/MySingleton#hello()." + range { + startLine = 5 + startCharacter = 24 + endLine = 5 + endCharacter = 29 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - // overload Counter * Counter - operator fun times(other: Counter) = Counter(this.value * other.value) + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/MySingleton#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "MySingleton" + signatureText = "public final object MySingleton : Any" + }, + scipSymbol { + symbol = "sample/MySingleton#hello()." + kind = Kind.Method + enclosingSymbol = "sample/MySingleton#" + displayName = "hello" + signatureText = "public final fun hello(): String" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } - // overload Counter * Int - operator fun times(value: Int) = Counter(this.value * value) + @Test + fun `companion object`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample - // overload Counter in Counter - operator fun contains(other: Counter) = other.value == this.value + class Foo { + companion object Factory { + fun create(): Foo = Foo() + } + } + fun use() = Foo.Factory.create() + """, + ) - // overload Counter[Int] = Int - operator fun set(index: Int, value: Int) { - this.value = index + value - } + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#" + range { + startLine = 2 + startCharacter = 6 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 6 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#Factory#" + range { + startLine = 3 + startCharacter = 21 + endLine = 3 + endCharacter = 28 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 5 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#Factory#create()." + range { + startLine = 4 + startCharacter = 12 + endLine = 4 + endCharacter = 18 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 33 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 15 + } + }, + // Foo.Factory is a FirResolvedQualifier spanning the full qualifier expression + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#Factory#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 23 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#Factory#create()." + range { + startLine = 7 + startCharacter = 24 + endLine = 7 + endCharacter = 30 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) - // overload Counter instance invocation - operator fun invoke() = println("The value of the counter is $'value'") + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Foo#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Foo" + signatureText = "public final class Foo : Any" + }, + scipSymbol { + symbol = "sample/Foo#Factory#" + kind = Kind.Class + enclosingSymbol = "sample/Foo#" + displayName = "Factory" + signatureText = "public final companion object Factory : Any" + }, + scipSymbol { + symbol = "sample/Foo#Factory#create()." + kind = Kind.Method + enclosingSymbol = "sample/Foo#Factory#" + displayName = "create" + signatureText = "public final fun create(): Foo" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } - } - /* You can also overload operators through extension methods */ - // overload -Counter - operator fun Counter.unaryMinus() = Counter(-this.value) + @Test + fun `unnamed companion object`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample - fun operatorOverloadingDemo() { - var counter1 = Counter(0) - var counter2 = Counter(5) - counter1 += 7 - println(counter1) // => Counter(value=7) - println(counter1 + counter2) // => Counter(value=12) - println(counter1 * counter2) // => Counter(value=35) - println(counter2 * 2) // => Counter(value=10) - println(counter1 in Counter(5)) // => false - println(counter1 in Counter(7)) // => true - counter1[26] = 10 - println(counter1) // => Counter(value=36) - counter1() // => The value of the counter is 36 - println(-counter2) // => Counter(value=-5) - } - """ + class Bar { + companion object { + fun instance(): Bar = Bar() + } + } + fun use() = Bar.instance() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#" + range { + startLine = 2 + startCharacter = 6 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 6 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#Companion#instance()." + range { + startLine = 4 + startCharacter = 12 + endLine = 4 + endCharacter = 20 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 35 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Bar#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 15 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Bar#Companion#instance()." + range { + startLine = 7 + startCharacter = 16 + endLine = 7 + endCharacter = 24 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#Companion#" + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 13 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 5 + } + }, ) + document.occurrencesList.shouldContainAll(*occurrences) - val result = - KotlinCompilation() - .apply { - sources = listOf(source) - compilerPluginRegistrars = listOf(AnalyzerRegistrar()) - verbose = false - pluginOptions = - listOf( - PluginOption("scip-kotlinc", "sourceroot", path.toString()), - PluginOption("scip-kotlinc", "targetroot", buildPath.toString()), - ) - commandLineProcessors = listOf(AnalyzerCommandLineProcessor()) - workingDir = path.toFile() - } - .compile() - - result.exitCode shouldBe KotlinCompilation.ExitCode.OK + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Bar#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Bar" + signatureText = "public final class Bar : Any" + }, + scipSymbol { + symbol = "sample/Bar#Companion#" + kind = Kind.Class + enclosingSymbol = "sample/Bar#" + displayName = "Companion" + signatureText = "public final companion object Companion : Any" + }, + scipSymbol { + symbol = "sample/Bar#Companion#instance()." + kind = Kind.Method + enclosingSymbol = "sample/Bar#Companion#" + displayName = "instance" + signatureText = "public final fun instance(): Bar" + }, + ) + document.symbolsList.shouldContainAll(*symbols) } @Test - fun `compound package name semicolon test`(@TempDir path: Path) { + fun `string template references`(@TempDir path: Path) { val document = compileScip( path, """ - package hello.sample; - class Apple - """ - .trimIndent(), + package sample + + fun greet(name: String) = "Hello, ${'$'}name!" + """, ) val occurrences = arrayOf( scipOccurrence { - role = REFERENCE - symbol = "hello/" + role = DEFINITION + symbol = "sample/greet()." range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 13 + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 41 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/greet().(name)" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 14 + } + enclosingRange { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 22 } }, scipOccurrence { role = REFERENCE - symbol = "hello/sample/" + symbol = "sample/greet().(name)" range { - startLine = 0 - startCharacter = 14 - endLine = 0 - endCharacter = 20 + startLine = 2 + startCharacter = 35 + endLine = 2 + endCharacter = 39 } }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/greet()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "greet" + signatureText = "public final fun greet(name: String): String" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `multiple supertype references`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + interface Named + interface Speakable + class Person : Named, Speakable + fun use(p: Person): Named = p + """, + ) + + val occurrences = + arrayOf( scipOccurrence { role = DEFINITION - symbol = "hello/sample/Apple#" + symbol = "sample/Person#" range { - startLine = 1 + startLine = 4 startCharacter = 6 - endLine = 1 - endCharacter = 11 + endLine = 4 + endCharacter = 12 } enclosingRange { - startLine = 1 - endLine = 1 - endCharacter = 11 + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 31 } }, scipOccurrence { - role = DEFINITION - symbol = "hello/sample/Apple#``()." + role = REFERENCE + symbol = "sample/Named#" range { - startLine = 1 - startCharacter = 6 - endLine = 1 - endCharacter = 11 + startLine = 4 + startCharacter = 15 + endLine = 4 + endCharacter = 20 } - enclosingRange { - startLine = 1 - endLine = 1 - endCharacter = 11 + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Speakable#" + range { + startLine = 4 + startCharacter = 22 + endLine = 4 + endCharacter = 31 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Named#" + range { + startLine = 5 + startCharacter = 20 + endLine = 5 + endCharacter = 25 } }, ) - document.occurrencesList.shouldContainAll(*occurrences) val symbols = arrayOf( scipSymbol { - symbol = "hello/sample/Apple#" - displayName = "Apple" - signatureText = "public final class Apple : Any" + symbol = "sample/Person#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Person" + addOverriddenSymbols("sample/Named#") + addOverriddenSymbols("sample/Speakable#") + signatureText = "public final class Person : Named, Speakable" } ) - document.symbolsList.shouldContainAll(*symbols) } @Test - fun `simple package name semicolon test`(@TempDir path: Path) { + fun `three-way overload disambiguator`(@TempDir path: Path) { val document = compileScip( path, """ - package sample; - class Banana { - fun foo() { } - }""", + package sample + + fun add(x: Int, y: Int): Int = x + y + fun add(x: Double, y: Double): Double = x + y + fun add(x: String, y: String): String = x + y + fun use() { + add(1, 2) + add(1.0, 2.0) + add("a", "b") + } + """, ) val occurrences = arrayOf( scipOccurrence { - role = REFERENCE - symbol = "sample/" + role = DEFINITION + symbol = "sample/add()." range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 14 + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 7 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 36 } }, scipOccurrence { role = DEFINITION - symbol = "sample/Banana#" + symbol = "sample/add(+1)." range { - startLine = 1 - startCharacter = 6 - endLine = 1 - endCharacter = 12 + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 7 } enclosingRange { - startLine = 1 + startLine = 3 + startCharacter = 0 endLine = 3 - endCharacter = 1 + endCharacter = 45 } }, scipOccurrence { role = DEFINITION - symbol = "sample/Banana#foo()." + symbol = "sample/add(+2)." range { - startLine = 2 - startCharacter = 8 - endLine = 2 - endCharacter = 11 + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 7 } enclosingRange { - startLine = 2 + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 45 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/add()." + range { + startLine = 6 startCharacter = 4 - endLine = 2 - endCharacter = 17 + endLine = 6 + endCharacter = 7 } }, scipOccurrence { - role = DEFINITION - symbol = "sample/Banana#" + role = REFERENCE + symbol = "sample/add(+1)." range { - startLine = 1 - startCharacter = 6 - endLine = 1 - endCharacter = 12 + startLine = 7 + startCharacter = 4 + endLine = 7 + endCharacter = 7 } - enclosingRange { - startLine = 1 - endLine = 3 - endCharacter = 1 + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/add(+2)." + range { + startLine = 8 + startCharacter = 4 + endLine = 8 + endCharacter = 7 } }, ) @@ -1384,48 +3006,34 @@ class AnalyzerTest { val symbols = arrayOf( scipSymbol { - symbol = "sample/Banana#" - displayName = "Banana" - signatureText = "public final class Banana : Any" + symbol = "sample/add()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: Int, y: Int): Int" }, scipSymbol { - symbol = "sample/Banana#foo()." - displayName = "foo" - signatureText = "public final fun foo(): Unit" + symbol = "sample/add(+1)." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: Double, y: Double): Double" + }, + scipSymbol { + symbol = "sample/add(+2)." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: String, y: String): String" }, ) document.symbolsList.shouldContainAll(*symbols) } - @Test - fun documentation(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - import java.io.Serializable - abstract class DocstringSuperclass - - /** Example class docstring */ - class Docstrings: DocstringSuperclass(), Serializable - - /** - * Example method docstring - * - **/ - inline fun docstrings(msg: String): Int { return msg.length } - """ - .trimIndent(), - ) - document.assertDocumentation("sample/Docstrings#", "Example class docstring") - document.assertDocumentation("sample/docstrings().", "Example method docstring") - } - private fun Document.assertDocumentation(symbol: String, expectedDocumentation: String) { val info = this.symbolsList.find { it.symbol == symbol } - ?: fail("no SymbolInformation for symbol $symbol") + ?: fail("no scipSymbol for symbol $symbol") val obtainedDocumentation = info.documentationList.joinToString("\n").trim() assertEquals(expectedDocumentation, obtainedDocumentation) } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt index 1e8ee95a7..fe8bdebc7 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt @@ -76,6 +76,8 @@ class ScipOccurrenceBuilder { @ScipBuilderDsl class ScipSymbolInformationBuilder { var symbol: String = "" + var kind: SymbolInformation.Kind = SymbolInformation.Kind.UnspecifiedKind + var enclosingSymbol: String = "" var displayName: String = "" var signatureText: String? = null private val docs = mutableListOf() @@ -95,6 +97,8 @@ class ScipSymbolInformationBuilder { internal fun build(): SymbolInformation = symbolInformation { symbol = this@ScipSymbolInformationBuilder.symbol + kind = this@ScipSymbolInformationBuilder.kind + enclosingSymbol = this@ScipSymbolInformationBuilder.enclosingSymbol if (this@ScipSymbolInformationBuilder.displayName.isNotEmpty()) { displayName = this@ScipSymbolInformationBuilder.displayName } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt index 048d33cee..0bc8349bf 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt @@ -3,6 +3,7 @@ package org.scip_code.scip_java.kotlinc.test import com.tschuchort.compiletesting.SourceFile import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.TestFactory +import org.scip_code.scip.SymbolInformation.Kind import org.scip_code.scip_java.kotlinc.* import org.scip_code.scip_java.kotlinc.test.ExpectedSymbols.ScipData import org.scip_code.scip_java.kotlinc.test.ExpectedSymbols.SymbolCacheData @@ -275,7 +276,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 0 startCharacter = 4 @@ -286,7 +287,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 0 startCharacter = 4 @@ -327,7 +328,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 0 startCharacter = 4 @@ -341,7 +342,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 1 startCharacter = 4 @@ -387,7 +388,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 0 startCharacter = 4 @@ -401,7 +402,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 1 startCharacter = 4 @@ -448,7 +449,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 1 startCharacter = 4 @@ -464,7 +465,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 2 startCharacter = 4 @@ -527,7 +528,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "Test#getSample()." + symbol = "Test#sample.get()." range { startLine = 0 startCharacter = 15 @@ -541,7 +542,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "Test#setSample()." + symbol = "Test#sample.set()." range { startLine = 0 startCharacter = 15 @@ -575,7 +576,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = REFERENCE - symbol = "Test#getSample()." + symbol = "Test#sample.get()." range { startLine = 2 startCharacter = 16 @@ -775,12 +776,16 @@ class ScipSymbolsTest { listOf( scipSymbol { symbol = "x." + kind = Kind.Property + enclosingSymbol = "_root_/" displayName = "x" signatureText = "public final val x: String" documentation("hello world\n test content") }, scipSymbol { - symbol = "getX()." + symbol = "x.get()." + kind = Kind.Method + enclosingSymbol = "x." displayName = "x" signatureText = "public get(): String" documentation("hello world\n test content") diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt index 4576e9893..5622fe594 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt @@ -193,6 +193,7 @@ fun scipVisitorAnalyzer( ) IrGenerationExtension.registerExtension( PostAnalysisExtension( + configuration = configuration, sourceRoot = sourceroot, targetRoot = Paths.get(""), callback = hook, @@ -200,6 +201,8 @@ fun scipVisitorAnalyzer( ) } + override val pluginId = PLUGIN_ID + override val supportsK2: Boolean get() = true } diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt index 7ee742752..288589564 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt @@ -5,52 +5,60 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``().(banana) // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getBanana(). -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#setBanana(). -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#setBanana().(value) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.set(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.set().(value) // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``().(apple) class Class constructor(private var banana: Int, apple: String) : // ^^^^^ definition scip-java maven . . snapshots/Class# +// kind Class // display_name Class // signature_documentation // > public final class Class : Throwable // relationship scip-java maven . . kotlin/Throwable# implementation // ^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(). +// kind Constructor // display_name Class // signature_documentation // > public constructor(banana: Int, apple: String): Class // ^^^^^^ definition scip-java maven . . snapshots/Class#``().(banana) +// kind Parameter // display_name banana // signature_documentation // > banana: Int // ^^^^^^ definition scip-java maven . . snapshots/Class#banana. +// kind Property // display_name banana // signature_documentation // > private final var banana: Int // ^^^^^^ reference scip-java maven . . snapshots/Class#``().(banana) -// ^^^^^^ definition scip-java maven . . snapshots/Class#getBanana(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.get(). +// kind Method // display_name banana // signature_documentation // > private get(): Int -// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set(). +// kind Method // display_name banana // signature_documentation // > private set(value: Int): Unit -// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana().(value) +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set().(value) +// kind Parameter // display_name value // signature_documentation // > value: Int // ^^^ reference scip-java maven . . kotlin/Int# // ^^^^^ definition scip-java maven . . snapshots/Class#``().(apple) +// kind Parameter // display_name apple // signature_documentation // > apple: String // ^^^^^^ reference scip-java maven . . kotlin/String# // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``().(banana) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getBanana(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#setBanana(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#setBanana().(value) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.set(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.set().(value) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``().(apple) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``(). Throwable(banana.toString() + apple) { @@ -65,43 +73,50 @@ } // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getAsdf(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf.get(). val asdf = // ^^^^ definition scip-java maven . . snapshots/Class#asdf. +// kind Property // display_name asdf // signature_documentation // > public final val asdf: Any -// ^^^^ definition scip-java maven . . snapshots/Class#getAsdf(). +// ^^^^ definition scip-java maven . . snapshots/Class#asdf.get(). +// kind Method // display_name asdf // signature_documentation // > public get(): Any -// ⌄ enclosing_range_start scip-java maven . . snapshots/``# -// ⌄ enclosing_range_start scip-java maven . . snapshots/``#``(). +// ⌄ enclosing_range_start local 0 +// ⌄ enclosing_range_start local 1 object { -// ^^^^^^ definition scip-java maven . . snapshots/``# +// ^^^^^^ definition local 0 +// kind Class // display_name // signature_documentation // > object : Any -// ^^^^^^ definition scip-java maven . . snapshots/``#``(). +// ^^^^^^ definition local 1 +// kind Constructor // display_name // signature_documentation // > private constructor(): -// ⌄ enclosing_range_start scip-java maven . . snapshots/``#doStuff(). +// ⌄ enclosing_range_start local 2 fun doStuff() = Unit -// ^^^^^^^ definition scip-java maven . . snapshots/``#doStuff(). +// ^^^^^^^ definition local 2 +// kind Method // display_name doStuff // signature_documentation // > public final fun doStuff(): Unit -// ⌃ enclosing_range_end scip-java maven . . snapshots/``#doStuff(). +// ^^^^ reference scip-java maven . . kotlin/Unit# +// ⌃ enclosing_range_end local 2 } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getAsdf(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/``# -// ⌃ enclosing_range_end scip-java maven . . snapshots/``#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf.get(). +// ⌃ enclosing_range_end local 0 +// ⌃ enclosing_range_end local 1 // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(+1). constructor() : this(1, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(+1). +// kind Constructor // display_name Class // signature_documentation // > public constructor(): Class @@ -111,10 +126,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(+2).(banana) constructor(banana: Int) : this(banana, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(+2). +// kind Constructor // display_name Class // signature_documentation // > public constructor(banana: Int): Class // ^^^^^^ definition scip-java maven . . snapshots/Class#``(+2).(banana) +// kind Parameter // display_name banana // signature_documentation // > banana: Int @@ -126,20 +143,22 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#run(). fun run() { // ^^^ definition scip-java maven . . snapshots/Class#run(). +// kind Method // display_name run // signature_documentation // > public final fun run(): Unit println(Class::class) // ^^^^^^^ reference scip-java maven . . kotlin/io/println(). +// ^^^^^ reference scip-java maven . . snapshots/Class# println("I eat $banana for lunch") // ^^^^^^^ reference scip-java maven . . kotlin/io/println(). // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. -// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.get(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). banana = 42 // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. -// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.get(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#run(). } diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt index d81175cdb..db2d168b9 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt @@ -5,27 +5,32 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#``(). class CompanionOwner { // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner# +// kind Class // display_name CompanionOwner // signature_documentation // > public final class CompanionOwner : Any // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#``(). +// kind Constructor // display_name CompanionOwner // signature_documentation // > public constructor(): CompanionOwner // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion# // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#``(). companion object { -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion# -// display_name Companion -// signature_documentation -// > public final companion object Companion : Any +// ^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion# +// kind Class +// display_name Companion +// signature_documentation +// > public final companion object Companion : Any // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#``(). +// kind Constructor // display_name Companion // signature_documentation // > private constructor(): CompanionOwner.Companion // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#create(). fun create(): CompanionOwner = CompanionOwner() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#create(). +// kind Method // display_name create // signature_documentation // > public final fun create(): CompanionOwner @@ -38,10 +43,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#create(). fun create(): Int = CompanionOwner.create().hashCode() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#create(). +// kind Method // display_name create // signature_documentation // > public final fun create(): Int // ^^^ reference scip-java maven . . kotlin/Int# +// ^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/CompanionOwner# // ^^^^^^ reference scip-java maven . . snapshots/CompanionOwner#Companion#create(). // ^^^^^^^^ reference scip-java maven . . kotlin/Any#hashCode(). // ⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#create(). diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt index b7a60c8a5..69c6333f2 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt @@ -10,10 +10,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/DocstringSuperclass#``(). abstract class DocstringSuperclass // ^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/DocstringSuperclass# +// kind Class // display_name DocstringSuperclass // signature_documentation // > public abstract class DocstringSuperclass : Any // ^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/DocstringSuperclass#``(). +// kind Constructor // display_name DocstringSuperclass // signature_documentation // > public constructor(): DocstringSuperclass @@ -24,6 +26,7 @@ /** Example class docstring. */ class Docstrings : DocstringSuperclass(), Serializable { // ^^^^^^^^^^ definition scip-java maven . . snapshots/Docstrings# +// kind Class // display_name Docstrings // signature_documentation // > public final class Docstrings : DocstringSuperclass, Serializable @@ -32,6 +35,7 @@ // relationship scip-java maven . . snapshots/DocstringSuperclass# implementation // relationship scip-java maven jdk 17 java/io/Serializable# implementation // ^^^^^^^^^^ definition scip-java maven . . snapshots/Docstrings#``(). +// kind Constructor // display_name Docstrings // signature_documentation // > public constructor(): Docstrings @@ -47,6 +51,7 @@ /** Example method docstring. */ fun docstrings() { } // ^^^^^^^^^^ definition scip-java maven . . snapshots/docstrings(). +// kind Method // display_name docstrings // signature_documentation // > public final fun docstrings(): Unit diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt index 2b9224e80..0ee9ed764 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt @@ -5,10 +5,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/sampleText().(x) fun sampleText(x: String = "") { // ^^^^^^^^^^ definition scip-java maven . . snapshots/sampleText(). +// kind Method // display_name sampleText // signature_documentation // > public final fun sampleText(x: String = ...): Unit // ^ definition scip-java maven . . snapshots/sampleText().(x) +// kind Parameter // display_name x // signature_documentation // > x: String = ... diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt index c9c88d892..0041a9f5a 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt @@ -5,11 +5,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Overrides#``(). class Overrides : AutoCloseable { // ^^^^^^^^^ definition scip-java maven . . snapshots/Overrides# +// kind Class // display_name Overrides // signature_documentation // > public final class Overrides : {kotlin/AutoCloseable=} AutoCloseable // relationship scip-java maven jdk 17 java/lang/AutoCloseable# implementation // ^^^^^^^^^ definition scip-java maven . . snapshots/Overrides#``(). +// kind Constructor // display_name Overrides // signature_documentation // > public constructor(): Overrides @@ -17,6 +19,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Overrides#close(). override fun close() { // ^^^^^ definition scip-java maven . . snapshots/Overrides#close(). +// kind Method // display_name close // signature_documentation // > public open override fun close(): Unit @@ -32,26 +35,30 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Animal# interface Animal { // ^^^^^^ definition scip-java maven . . snapshots/Animal# +// kind Interface // display_name Animal // signature_documentation // > public abstract interface Animal : Any // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber.get(). val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public abstract val favoriteNumber: Int -// ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#favoriteNumber.get(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ^^^ reference scip-java maven . . kotlin/Int# // ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#sound(). fun sound(): String // ^^^^^ definition scip-java maven . . snapshots/Animal#sound(). +// kind Method // display_name sound // signature_documentation // > public abstract fun sound(): String @@ -64,11 +71,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Bird#``(). open class Bird : Animal { // ^^^^ definition scip-java maven . . snapshots/Bird# +// kind Class // display_name Bird // signature_documentation // > public open class Bird : Animal // relationship scip-java maven . . snapshots/Animal# implementation // ^^^^ definition scip-java maven . . snapshots/Bird#``(). +// kind Constructor // display_name Bird // signature_documentation // > public constructor(): Bird @@ -76,22 +85,25 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Bird#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#favoriteNumber.get(). get() = 42 -// ^^^ definition scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ^^^ definition scip-java maven . . snapshots/Bird#favoriteNumber.get(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Bird#sound(). +// kind Method // display_name sound // signature_documentation // > public open override fun sound(): String @@ -107,11 +119,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#``(). class Seagull : Bird() { // ^^^^^^^ definition scip-java maven . . snapshots/Seagull# +// kind Class // display_name Seagull // signature_documentation // > public final class Seagull : Bird // relationship scip-java maven . . snapshots/Bird# implementation // ^^^^^^^ definition scip-java maven . . snapshots/Seagull#``(). +// kind Constructor // display_name Seagull // signature_documentation // > public constructor(): Seagull @@ -119,21 +133,24 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Seagull#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#favoriteNumber.get(). get() = 1337 -// ^^^ definition scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ^^^ definition scip-java maven . . snapshots/Seagull#favoriteNumber.get(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Seagull#sound(). +// kind Method // display_name sound // signature_documentation // > public open override fun sound(): String diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt index 0d80eb5bc..1748c0b3b 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt @@ -2,25 +2,29 @@ // ^^^^^^^^^ reference scip-java maven . . snapshots/ //⌄ enclosing_range_start scip-java maven . . snapshots/x. -//⌄ enclosing_range_start scip-java maven . . snapshots/getX(). +//⌄ enclosing_range_start scip-java maven . . snapshots/x.get(). // ⌄ enclosing_range_start local 0 // ⌄ enclosing_range_start local 1 val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } // ^ definition scip-java maven . . snapshots/x. +// kind Property // display_name x // signature_documentation // > public final val x: Unit -// ^ definition scip-java maven . . snapshots/getX(). +// ^ definition scip-java maven . . snapshots/x.get(). +// kind Method // display_name x // signature_documentation // > public get(): Unit // ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/arrayListOf(). -// ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/forEachIndexed(+9). +// ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/Iterable#forEachIndexed(). // ^ definition local 0 +// kind Parameter // display_name i // signature_documentation // > i: Int // ^ definition local 1 +// kind Parameter // display_name s // signature_documentation // > s: String @@ -30,47 +34,52 @@ // ⌃ enclosing_range_end local 0 // ⌃ enclosing_range_end local 1 // ⌃ enclosing_range_end scip-java maven . . snapshots/x. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getX(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/x.get(). //⌄ enclosing_range_start scip-java maven . . snapshots/y. -//⌄ enclosing_range_start scip-java maven . . snapshots/getY(). +//⌄ enclosing_range_start scip-java maven . . snapshots/y.get(). val y = "fdsa".run { this.toByteArray() } // ^ definition scip-java maven . . snapshots/y. +// kind Property // display_name y // signature_documentation // > public final val y: ByteArray -// ^ definition scip-java maven . . snapshots/getY(). +// ^ definition scip-java maven . . snapshots/y.get(). +// kind Method // display_name y // signature_documentation // > public get(): ByteArray // ^^^ reference scip-java maven . . kotlin/run(+1). -// ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/toByteArray(). +// ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/String#toByteArray(). // ⌃ enclosing_range_end scip-java maven . . snapshots/y. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getY(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/y.get(). //⌄ enclosing_range_start scip-java maven . . snapshots/z. -//⌄ enclosing_range_start scip-java maven . . snapshots/getZ(). +//⌄ enclosing_range_start scip-java maven . . snapshots/z.get(). // ⌄ enclosing_range_start local 2 val z = y.let { it.size } // ^ definition scip-java maven . . snapshots/z. +// kind Property // display_name z // signature_documentation // > public final val z: Int -// ^ definition scip-java maven . . snapshots/getZ(). +// ^ definition scip-java maven . . snapshots/z.get(). +// kind Method // display_name z // signature_documentation // > public get(): Int // ^ reference scip-java maven . . snapshots/y. -// ^ reference scip-java maven . . snapshots/getY(). +// ^ reference scip-java maven . . snapshots/y.get(). // ^^^ reference scip-java maven . . kotlin/let(). // ^^^^^^^^^^^ definition local 2 +// kind Parameter // display_name it // signature_documentation // > it: ByteArray // ^^ reference local 2 // ^^^^ reference scip-java maven . . kotlin/ByteArray#size. -// ^^^^ reference scip-java maven . . kotlin/ByteArray#getSize(). +// ^^^^ reference scip-java maven . . kotlin/ByteArray#size.get(). // ⌃ enclosing_range_end scip-java maven . . snapshots/z. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getZ(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/z.get(). // ⌃ enclosing_range_end local 2 diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt index 3b803c7c3..6211a42e2 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt @@ -10,10 +10,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#``(). object ObjectKt { // ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt# +// kind Class // display_name ObjectKt // signature_documentation // > public final object ObjectKt : Any // ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt#``(). +// kind Constructor // display_name ObjectKt // signature_documentation // > private constructor(): ObjectKt @@ -21,10 +23,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#fail().(message) fun fail(message: String?): Nothing { // ^^^^ definition scip-java maven . . snapshots/ObjectKt#fail(). +// kind Method // display_name fail // signature_documentation // > public final fun fail(message: String?): Nothing // ^^^^^^^ definition scip-java maven . . snapshots/ObjectKt#fail().(message) +// kind Parameter // display_name message // signature_documentation // > message: String?