diff --git a/.github/renovate.json b/.github/renovate.json index 91989653d..b22febb91 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,5 +4,16 @@ "config:recommended" ], "automerge": true, - "semanticCommits": false + "semanticCommits": false, + "packageRules": [ + { + "description": "The Analysis API engine pin is managed manually: it lives in the JetBrains intellij-dependencies repository, uses IDE-aligned version schemes, and must move in lockstep with the `kotlin` toolchain version.", + "matchPackageNames": [ + "org.jetbrains.kotlin:kotlin-compiler", + "*-for-ide", + "com.intellij.platform:*" + ], + "enabled": false + } + ] } diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 536988c8b..e09d2b5ac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,9 +22,9 @@ jobs: - run: gradle spotlessCheck --no-daemon - kotlin_plugin: + kotlin_indexer: runs-on: ubuntu-latest - name: scip-kotlinc + name: scip-kotlin-analysis steps: - uses: actions/checkout@v7 @@ -37,8 +37,8 @@ jobs: with: gradle-version: 9.8.2 - - name: scip-kotlinc tests - run: gradle :scip-kotlinc:test --no-daemon + - name: scip-kotlin-analysis tests + run: gradle :scip-kotlin-analysis:test --no-daemon docker_test: runs-on: ubuntu-latest @@ -69,7 +69,7 @@ jobs: ci-pass: if: always() - needs: [format, kotlin_plugin, docker_test] + needs: [format, kotlin_indexer, docker_test] runs-on: ubuntu-latest name: CI pass steps: diff --git a/.github/workflows/repos.yaml b/.github/workflows/repos.yaml index f3d0792dd..a9fdf629f 100644 --- a/.github/workflows/repos.yaml +++ b/.github/workflows/repos.yaml @@ -119,6 +119,39 @@ jobs: expected_language: kotlin covers: Gradle, Kotlin multiplatform JVM target, Kotlin 2.4.0, Java 25 runtime + - name: gradle-kotlin19-kotlinpoet + repository: square/kotlinpoet + ref: 1.16.0 + directory: . + build_tool: gradle + build_command: ":kotlinpoet:compileKotlinJvm" + java: 17 + bazel_version: "" + expected_language: kotlin + covers: Gradle, Kotlin multiplatform JVM target, legacy Kotlin 1.9.22, Java 17 runtime + + - name: gradle-kotlin23-turbine + repository: cashapp/turbine + ref: ab1e9a0acbbbb175081a0c5d94cb4f4cf4da444a + directory: . + build_tool: gradle + build_command: "compileKotlinJvm" + java: 17 + bazel_version: "" + expected_language: kotlin + covers: Gradle, Kotlin multiplatform JVM target, Kotlin 2.3.21, Java 17 runtime + + - name: gradle-kotlin24-detekt + repository: detekt/detekt + ref: 04f97401d462e899383156b25d026b7b8c181401 + directory: . + build_tool: gradle + build_command: ":detekt-api:compileKotlin" + java: 21 + bazel_version: "" + expected_language: kotlin + covers: Gradle, Kotlin JVM, Kotlin 2.4.0, settings repositories (FAIL_ON_PROJECT_REPOS), Java 21 runtime + - name: gradle-mixed-kotlinpoet repository: square/kotlinpoet ref: 0182a25089ee1f4f2378568b497f82a8ebc3017d diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85c7f09c1..ba814e94d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,7 +54,7 @@ These are the main components of the project. | ------------------------------------------------------- | -------- | ----------------------------------------------------------------------- | | `gradle test --no-daemon` | terminal | Run all Gradle tests. | | `gradle :scip-java:test --no-daemon` | terminal | Run CLI build-tool integration tests (Gradle, Maven, SCIP config). | -| `gradle :scip-kotlinc:test --no-daemon` | terminal | Run Kotlin compiler-plugin tests. | +| `gradle :scip-kotlin-analysis:test --no-daemon` | terminal | Run Kotlin indexer tests. | | `gradle :scip-snapshots:test --no-daemon` | terminal | Compare Java and Kotlin snapshot goldens. | | `gradle :scip-snapshots:saveSnapshots --no-daemon` | terminal | Regenerate Java and Kotlin snapshot goldens. | | `gradle :scip-java:installDist --no-daemon` | terminal | Build a local `scip-java` distribution under `scip-java/build/install/`. | diff --git a/build-logic/src/main/kotlin/org/scip_code/scip_java/buildlogic/ScipCompile.kt b/build-logic/src/main/kotlin/org/scip_code/scip_java/buildlogic/ScipCompile.kt index 079e9b877..7d766e266 100644 --- a/build-logic/src/main/kotlin/org/scip_code/scip_java/buildlogic/ScipCompile.kt +++ b/build-logic/src/main/kotlin/org/scip_code/scip_java/buildlogic/ScipCompile.kt @@ -4,7 +4,6 @@ import java.io.File import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.file.Directory -import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider import org.gradle.api.tasks.compile.JavaCompile @@ -27,29 +26,6 @@ fun JavaCompile.useScipJavac( (options.forkOptions.jvmArgs ?: emptyList()) + JavacInternals.jvmOptions(rootDir) } -/** - * Builds the `kotlinc` arguments that load the scip-kotlinc compiler plugin from [pluginClasspath] - * (the resolved shaded jar) and point it at [sourceroot]/[targetroot]. - * - * The mapping lives here, in compiled build logic, rather than in a build script: a `.map {}` - * lambda declared in a `.gradle.kts` file captures a hidden reference to the script object, which - * the configuration cache cannot serialize. - */ -fun scipKotlincPluginArgs( - pluginClasspath: Provider>, - sourceroot: String, - targetroot: String, -): Provider> = - pluginClasspath.map { locations -> - listOf( - "-Xplugin=${locations.single().asFile.absolutePath}", - "-P", - "plugin:scip-kotlinc:sourceroot=$sourceroot", - "-P", - "plugin:scip-kotlinc:targetroot=$targetroot", - ) - } - /** * Registers a `doFirst` action that empties [dir] (deletes then recreates it) before the task runs. * diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5196d3f0..3db7eb7a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,9 +2,10 @@ clikt = "5.1.0" gradle-api = "8.11.1" junit-jupiter = "5.11.4" -kctfork = "0.14.0" -kotest = "6.2.6" kotlin = "2.4.21" +kotlin-analysis-api = "2.4.0" +kotlinx-collections-immutable = "0.3.8" +kotlinx-coroutines-intellij = "1.8.0-intellij-13" kotlinx-serialization = "1.12.0" lombok = "1.18.48" maven-plugin-annotations = "3.16.0" @@ -18,20 +19,24 @@ spotless = "8.10.4" vanniktech-maven-publish = "0.37.0" [libraries] +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version = "2.9.3" } clikt-jvm = { module = "com.github.ajalt.clikt:clikt-jvm", version.ref = "clikt" } gradle-api = { module = "dev.gradleplugins:gradle-api", version.ref = "gradle-api" } gradle-test-kit = { module = "dev.gradleplugins:gradle-test-kit", version.ref = "gradle-api" } -kctfork-core = { module = "dev.zacsweers.kctfork:core", version.ref = "kctfork" } -kotest-assertions-core = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest" } -kotlin-compiler-embeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } -kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } -kotlin-scripting-common = { module = "org.jetbrains.kotlin:kotlin-scripting-common", version.ref = "kotlin" } -kotlin-scripting-dependencies = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies", version.ref = "kotlin" } -kotlin-scripting-dependencies-maven = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies-maven", version.ref = "kotlin" } -kotlin-scripting-jvm = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm", version.ref = "kotlin" } +kotlin-analysis-api-api = { module = "org.jetbrains.kotlin:analysis-api-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-api-impl-base = { module = "org.jetbrains.kotlin:analysis-api-impl-base-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-api-k2 = { module = "org.jetbrains.kotlin:analysis-api-k2-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-api-platform-interface = { module = "org.jetbrains.kotlin:analysis-api-platform-interface-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-api-standalone = { module = "org.jetbrains.kotlin:analysis-api-standalone-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-compiler = { module = "org.jetbrains.kotlin:kotlin-compiler", version.ref = "kotlin-analysis-api" } +kotlin-analysis-compiler-common = { module = "org.jetbrains.kotlin:kotlin-compiler-common-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-low-level-api-fir = { module = "org.jetbrains.kotlin:low-level-api-fir-for-ide", version.ref = "kotlin-analysis-api" } +kotlin-analysis-symbol-light-classes = { module = "org.jetbrains.kotlin:symbol-light-classes-for-ide", version.ref = "kotlin-analysis-api" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" } +kotlinx-collections-immutable-jvm = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm", version.ref = "kotlinx-collections-immutable" } +kotlinx-coroutines-core-jvm = { module = "com.intellij.platform:kotlinx-coroutines-core-jvm", version.ref = "kotlinx-coroutines-intellij" } kotlinx-serialization-json-jvm = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version.ref = "kotlinx-serialization" } lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } maven-plugin-annotations = { module = "org.apache.maven.plugin-tools:maven-plugin-annotations", version.ref = "maven-plugin-annotations" } diff --git a/scip-gradle-plugin/src/main/java/org/scip_code/scip_java/gradle/ScipGradlePlugin.java b/scip-gradle-plugin/src/main/java/org/scip_code/scip_java/gradle/ScipGradlePlugin.java index 8ad6ddfa7..78c605189 100644 --- a/scip-gradle-plugin/src/main/java/org/scip_code/scip_java/gradle/ScipGradlePlugin.java +++ b/scip-gradle-plugin/src/main/java/org/scip_code/scip_java/gradle/ScipGradlePlugin.java @@ -1,5 +1,9 @@ package org.scip_code.scip_java.gradle; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -7,6 +11,7 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.FileCollection; import org.gradle.api.initialization.Settings; import org.gradle.api.internal.GradleInternal; import org.gradle.api.tasks.compile.JavaCompile; @@ -110,15 +115,14 @@ private void configureProject(Project project) { triggers.add("compileKotlin"); triggers.add("compileTestKotlin"); } - - // The CLI's init script provides the path of the embedded scip-kotlinc jar. - Object scipKotlinc = requiredExtra(extraProperties, "scipKotlincJar"); - project - .getTasks() - .configureEach( - task -> configureKotlinCompileTask(task, scipKotlinc, sourceRoot, targetRoot)); } + // Not limited to the plugin checks above: AGP 9's built-in Kotlin adds + // Kotlin compile tasks to Android modules without applying a Kotlin plugin. + project + .getTasks() + .configureEach(task -> configureKotlinCompileTask(task, sourceRoot, targetRoot)); + project.getTasks().create("scipCompileAll").dependsOn(triggers); project.getTasks().create("scipPrintDependencies", WriteDependencies.class); @@ -172,39 +176,72 @@ private static boolean tryAddJavacPlugin(Project project, Object javacPluginDep) } } - private static void configureKotlinCompileTask( - Task task, Object scipKotlinc, String sourceRoot, String targetRoot) { + /** + * Kotlin sources are not indexed inside kotlinc: after each Kotlin compile task runs, its sources + * and classpath are dumped to {@code /kotlin-configs/.txt} for the scip-java + * CLI to index after the build, decoupling indexing from the build's Kotlin compiler version. + */ + private static void configureKotlinCompileTask(Task task, String sourceRoot, String targetRoot) { if (!task.getClass().getSimpleName().contains("KotlinCompile")) { return; } - // Referring to KotlinCompile directly here triggers NoClassDefFoundError - - // the plugin classpath is murky and we deliberately don't bundle the Kotlin - // Gradle plugin. So we commit the sins of reflection for our limited needs. - try { - Object kotlinOptions = task.getClass().getMethod("getKotlinOptions").invoke(task); - - @SuppressWarnings("unchecked") - List freeCompilerArgs = - (List) - kotlinOptions.getClass().getMethod("getFreeCompilerArgs").invoke(kotlinOptions); - - List newArgs = new ArrayList<>(freeCompilerArgs.size() + 5); - newArgs.addAll(freeCompilerArgs); - newArgs.add("-Xplugin=" + scipKotlinc); - newArgs.add("-P"); - newArgs.add("plugin:scip-kotlinc:sourceroot=" + sourceRoot); - newArgs.add("-P"); - newArgs.add("plugin:scip-kotlinc:targetroot=" + targetRoot); - - kotlinOptions - .getClass() - .getMethod("setFreeCompilerArgs", List.class) - .invoke(kotlinOptions, newArgs); - } catch (ReflectiveOperationException exc) { - throw new RuntimeException( - "scip-java: failed to configure Kotlin compile task '" + task.getName() + "'", exc); - } + task.doLast( + ignored -> { + try { + List lines = new ArrayList<>(); + lines.add("sourceroot " + sourceRoot); + + // Referring to KotlinCompile directly triggers NoClassDefFoundError - + // the plugin classpath is murky and we deliberately don't bundle the + // Kotlin Gradle plugin. So we commit the sins of reflection. + FileCollection libraries; + try { + libraries = (FileCollection) task.getClass().getMethod("getLibraries").invoke(task); + } catch (ReflectiveOperationException exc) { + libraries = (FileCollection) task.getClass().getMethod("getClasspath").invoke(task); + } + for (File entry : libraries.getFiles()) { + lines.add("classpath " + entry.getAbsolutePath()); + } + + int sources = 0; + for (File file : task.getInputs().getSourceFiles().getFiles()) { + if (file.getName().endsWith(".kt") || file.getName().endsWith(".kts")) { + lines.add("source " + file.getAbsolutePath()); + sources++; + } + } + // Java sources of the same module are listed too, so Kotlin + // references to them resolve. The indexer only emits Kotlin files. + try { + FileCollection javaSources = + (FileCollection) task.getClass().getMethod("getJavaSources").invoke(task); + for (File file : javaSources.getFiles()) { + if (file.getName().endsWith(".java")) { + lines.add("source " + file.getAbsolutePath()); + } + } + } catch (ReflectiveOperationException exc) { + // Kotlin Gradle Plugin without javaSources (pre 1.7). + } + if (sources == 0) { + return; + } + + Path configDir = Paths.get(targetRoot, "kotlin-configs"); + Files.createDirectories(configDir); + String fileName = task.getPath().replaceAll("[^A-Za-z0-9]", "-") + ".txt"; + Files.write(configDir.resolve(fileName), lines); + } catch (Exception exc) { + throw new RuntimeException( + "scip-java: failed to extract Kotlin compile configuration for task '" + + task.getName() + + "': " + + exc, + exc); + } + }); } private static Object requiredExtra(Map extraProperties, String name) { diff --git a/scip-java/build.gradle.kts b/scip-java/build.gradle.kts index 835e24adc..26111ee58 100644 --- a/scip-java/build.gradle.kts +++ b/scip-java/build.gradle.kts @@ -13,17 +13,12 @@ description = "Java and Kotlin indexer for SCIP" val javacShadowJar = shadowJarArtifact(":scip-javac", "javacShadowJar") val gradlePluginShadowJar = shadowJarArtifact(":scip-gradle-plugin", "gradlePluginShadowJar") -val kotlincShadowJar = shadowJarArtifact(":scip-kotlinc", "kotlincShadowJar") dependencies { implementation(project(":scip-aggregator")) + implementation(project(":scip-kotlin-analysis")) implementation(libs.clikt.jvm) implementation(libs.kotlin.stdlib) - implementation(libs.kotlin.compiler.embeddable) - implementation(libs.kotlin.scripting.common) - implementation(libs.kotlin.scripting.jvm) - implementation(libs.kotlin.scripting.dependencies) - implementation(libs.kotlin.scripting.dependencies.maven) implementation(libs.kotlinx.serialization.json.jvm) testImplementation(libs.kotlin.test) @@ -46,9 +41,6 @@ val generateEmbeddedResources = tasks.register("generateEmbeddedResources" from(gradlePluginShadowJar) { rename { "gradle-plugin.jar" } } - from(kotlincShadowJar) { - rename { "scip-kotlinc.jar" } - } into(layout.buildDirectory.dir("generated/resources/embedded")) } diff --git a/scip-java/src/main/kotlin/org/scip_code/scip_java/Embedded.kt b/scip-java/src/main/kotlin/org/scip_code/scip_java/Embedded.kt index caf9b4f50..2d86b8074 100644 --- a/scip-java/src/main/kotlin/org/scip_code/scip_java/Embedded.kt +++ b/scip-java/src/main/kotlin/org/scip_code/scip_java/Embedded.kt @@ -25,8 +25,6 @@ object Embedded { fun gradlePluginJar(tmpDir: Path): Path = copyFile(tmpDir, "gradle-plugin.jar") - fun scipKotlincJar(tmpDir: Path): Path = copyFile(tmpDir, "scip-kotlinc.jar") - private fun javacErrorpath(tmp: Path): Path = tmp.resolve("errorpath.txt") data class CustomJavac(val executable: Path, val environment: Map) diff --git a/scip-java/src/main/kotlin/org/scip_code/scip_java/ScipJava.kt b/scip-java/src/main/kotlin/org/scip_code/scip_java/ScipJava.kt index a15ba62a4..2e6ddbde6 100644 --- a/scip-java/src/main/kotlin/org/scip_code/scip_java/ScipJava.kt +++ b/scip-java/src/main/kotlin/org/scip_code/scip_java/ScipJava.kt @@ -1,6 +1,7 @@ package org.scip_code.scip_java import java.io.PrintStream +import kotlin.system.exitProcess /** * Public entry point for the scip-java CLI. The single [app] instance is shared across test suites @@ -17,6 +18,8 @@ object ScipJava { @JvmStatic fun main(args: Array) { app.runAndExitIfNonZero(args.toList()) + // The Analysis API indexer leaves non-daemon threads behind; exit explicitly. + exitProcess(0) } fun printHelp(out: PrintStream) { diff --git a/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/GradleBuildTool.kt b/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/GradleBuildTool.kt index 900287442..432c17af9 100644 --- a/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/GradleBuildTool.kt +++ b/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/GradleBuildTool.kt @@ -6,6 +6,7 @@ import java.nio.file.Path import java.nio.file.Paths import org.scip_code.scip_java.Embedded import org.scip_code.scip_java.commands.IndexCommand +import org.scip_code.scip_java.kotlin_analysis.KotlinAnalysisIndexer class GradleBuildTool(index: IndexCommand) : BuildTool("Gradle", index) { @@ -89,19 +90,62 @@ This means our SCIP compiler plugin was not attached to one or more JavaCompile cmd += "--no-daemon" cmd += "--init-script" cmd += script - cmd += "-Pkotlin.compiler.execution.strategy=in-process" cmd += "-Dscip.targetroot=${targetroot()}" cmd += index.finalBuildCommand(listOf("clean", "scipPrintDependencies", "scipCompileAll")) targetroot().toFile().deleteRecursively() val result = index.app.runProcess(cmd, env = mapOf("TERM" to "dumb")) + indexKotlinConfigs() return Embedded.reportUnexpectedJavacErrors(index.app.reporter, tmp) ?: result } + /** + * Indexes the sources/classpath dumps the Gradle plugin wrote to `/kotlin-configs/` + * after each Kotlin compile task. + */ + private fun indexKotlinConfigs() { + val configsDir = targetroot().resolve("kotlin-configs") + if (!Files.isDirectory(configsDir)) return + Files.list(configsDir).use { stream -> + for (config in stream.filter { it.fileName.toString().endsWith(".txt") }) { + try { + indexKotlinConfig(config) + } catch (e: Exception) { + index.app.reporter.error( + RuntimeException("failed to index Kotlin sources from $config", e) + ) + } + } + } + } + + private fun indexKotlinConfig(config: Path) { + var sourceroot: Path = index.workingDirectory + val classpath = mutableListOf() + val sources = mutableListOf() + for (line in Files.readAllLines(config)) { + val separator = line.indexOf(' ') + if (separator < 0) continue + val value = line.substring(separator + 1) + when (line.substring(0, separator)) { + "sourceroot" -> sourceroot = Paths.get(value) + "classpath" -> classpath.add(Paths.get(value)) + "source" -> sources.add(Paths.get(value)) + } + } + if (sources.isEmpty()) return + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot(), + sourceRoots = sources, + classpath = classpath, + ) + .run() + } + private fun initScript(tmp: Path): Path { val pluginpath = Embedded.scipJar(tmp) val gradlePluginPath = Embedded.gradlePluginJar(tmp) - val scipKotlincPath = Embedded.scipKotlincJar(tmp) val dependenciesPath = targetroot().resolve("dependencies.txt") Files.deleteIfExists(dependenciesPath) @@ -119,7 +163,6 @@ This means our SCIP compiler plugin was not attached to one or more JavaCompile project.ext["scipTarget"] = "${targetroot()}" project.ext["javacPluginJar"] = "$pluginpath" project.ext["dependenciesOut"] = "$dependenciesPath" - project.ext["scipKotlincJar"] = "$scipKotlincPath" apply plugin: ScipGradlePlugin } """ diff --git a/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/ScipBuildTool.kt b/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/ScipBuildTool.kt index 2c06f9d41..cf3fe7650 100644 --- a/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/ScipBuildTool.kt +++ b/scip-java/src/main/kotlin/org/scip_code/scip_java/buildtools/ScipBuildTool.kt @@ -17,16 +17,9 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.boolean import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonArray -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation -import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.cli.common.messages.MessageRenderer -import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler -import org.jetbrains.kotlin.config.Services import org.scip_code.scip_java.Embedded import org.scip_code.scip_java.commands.IndexCommand +import org.scip_code.scip_java.kotlin_analysis.KotlinAnalysisIndexer /** * A custom build tool that is specifically made for scip-java. @@ -130,7 +123,7 @@ class ScipBuildTool(index: IndexCommand) : BuildTool("SCIP", index) { val errors = mutableListOf() compileJavaFiles(tmp, config, javaFiles)?.let { errors += it } - compileKotlinFiles(config, kotlinFiles, tmp)?.let { errors += it } + indexKotlinFiles(config, kotlinFiles)?.let { errors += it } if (index.cleanup) { tmp.toFile().deleteRecursively() @@ -154,93 +147,28 @@ class ScipBuildTool(index: IndexCommand) : BuildTool("SCIP", index) { } } - private fun compileKotlinFiles( - config: Config, - allKotlinFiles: List, - tmp: Path, - ): Throwable? { + private fun indexKotlinFiles(config: Config, allKotlinFiles: List): Throwable? { if (allKotlinFiles.isEmpty()) return null val sourceroot = index.workingDirectory - val filesPaths = allKotlinFiles.map { it.toString() } - - // The scip-kotlinc compiler plugin is built and shipped together - // with the scip-java CLI as an embedded resource (see Embedded.kt and - // the :scip-java Gradle resources wiring). - val plugin = Embedded.scipKotlincJar(tmp) - - val classpath = - config.classpath.joinToString(File.pathSeparator) { - index.workingDirectory.resolve(it).toString() - } - - val kargs = K2JVMCompilerArguments() - val args = - mutableListOf( - "-nowarn", - "-no-reflect", - "-no-stdlib", - "-Xmulti-platform", - "-Xno-check-actual", - "-verbose:class", - "-opt-in=kotlin.RequiresOptIn", - "-opt-in=kotlin.ExperimentalUnsignedTypes", - "-opt-in=kotlin.ExperimentalStdlibApi", - "-opt-in=kotlin.ExperimentalMultiplatform", - "-opt-in=kotlin.contracts.ExperimentalContracts", - "-Xallow-kotlin-package", - "-Xplugin=$plugin", - "-P", - "plugin:scip-kotlinc:sourceroot=$sourceroot", - "-P", - "plugin:scip-kotlinc:targetroot=$targetroot", - "-classpath", - classpath, - ) - args += filesPaths - - parseCommandLineArguments(args, kargs) - - val exit = - K2JVMCompiler() - .exec( - object : MessageCollector { - private var sawError = false - - override fun clear() { - sawError = false - } - - override fun hasErrors(): Boolean = sawError - - override fun report( - severity: CompilerMessageSeverity, - message: String, - location: CompilerMessageSourceLocation?, - ) { - if ( - message.endsWith("without a body must be abstract") || - message.endsWith("must have a body") - ) { - // We get these when indexing the stdlib; - // no other solution found yet. - return - } - val rendered = - MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location) - index.app.reporter.debug(rendered) - // Only treat ERROR / EXCEPTION as failures. - // Kotlin 2.2.0's K2JVMCompiler emits LOGGING/INFO/WARNING - // messages during normal compilation; pushing those onto - // `errors` would cause hasErrors to return true. - if (severity.isError) { - sawError = true - } - } - }, - Services.EMPTY, - kargs, + val classpath = config.classpath.map { sourceroot.resolve(it).normalize() } + val jdkHome = + config.javaHome?.let { Paths.get(it) } ?: Paths.get(System.getProperty("java.home")) + // The Analysis API indexer does not compile the sources: unresolved code + // degrades individual occurrences instead of failing the build, so only + // indexer crashes surface as errors. + return try { + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot, + sourceRoots = allKotlinFiles, + classpath = classpath, + jdkHome = jdkHome, ) - return if (exit.code == 0) null else Exception(exit.toString()) + .run() + null + } catch (e: Exception) { + e + } } private fun compileJavaFiles(tmp: Path, config: Config, allJavaFiles: List): Throwable? { diff --git a/scip-kotlin-analysis/build.gradle.kts b/scip-kotlin-analysis/build.gradle.kts new file mode 100644 index 000000000..f500d908c --- /dev/null +++ b/scip-kotlin-analysis/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + id("scip.java-library") + id("scip.kotlin-jvm") +} + +description = "Standalone Kotlin SCIP indexer built on the Kotlin Analysis API" + +// The Analysis API `*-for-ide` artifacts have unusable POM metadata (they drag in +// IDE-only dependencies), so every artifact is pinned non-transitively — the same +// approach used by Dokka's analysis-kotlin-symbols and KSP2. `kotlin-compiler` +// (the non-embeddable jar) provides the bundled IntelliJ core classes at runtime. +dependencies { + implementation(project(":scip-shared")) + implementation(libs.scip.kotlin.bindings) + + implementation(libs.kotlin.analysis.compiler) { isTransitive = false } + implementation(libs.kotlin.analysis.api.api) { isTransitive = false } + implementation(libs.kotlin.analysis.api.standalone) { isTransitive = false } + + runtimeOnly(libs.kotlin.analysis.api.impl.base) { isTransitive = false } + runtimeOnly(libs.kotlin.analysis.api.k2) { isTransitive = false } + // Provides org.jetbrains.kotlin.analysis.decompiler.* which is no longer + // bundled in kotlin-compiler as of the 2.4 line. + runtimeOnly(libs.kotlin.analysis.compiler.common) { isTransitive = false } + runtimeOnly(libs.kotlin.analysis.low.level.api.fir) { isTransitive = false } + runtimeOnly(libs.kotlin.analysis.api.platform.`interface`) { isTransitive = false } + runtimeOnly(libs.kotlin.analysis.symbol.light.classes) { isTransitive = false } + runtimeOnly(libs.caffeine) + runtimeOnly(libs.kotlinx.coroutines.core.jvm) + runtimeOnly(libs.kotlinx.collections.immutable.jvm) + runtimeOnly(libs.kotlinx.serialization.json.jvm) + + testImplementation(libs.kotlin.test) + testImplementation(libs.kotlin.test.junit5) +} diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt new file mode 100644 index 000000000..1c1dd00e4 --- /dev/null +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexer.kt @@ -0,0 +1,1185 @@ +package org.scip_code.scip_java.kotlin_analysis + +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Arrays +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource +import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaPropertySymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolOrigin +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.psi.KtCatchClause +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor +import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtEnumEntrySuperclassReferenceExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtParameterList +import org.jetbrains.kotlin.psi.KtPrimaryConstructor +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPropertyAccessor +import org.jetbrains.kotlin.psi.KtSecondaryConstructor +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtTypeAlias +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtVariableDeclaration +import org.jetbrains.kotlin.psi.KtWhenExpression +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.types.Variance +import org.scip_code.scip.Document +import org.scip_code.scip.Occurrence +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 +import org.scip_code.scip.symbolInformation +import org.scip_code.scip_java.shared.ScipDocumentBuilder +import org.scip_code.scip_java.shared.ScipRange +import org.scip_code.scip_java.shared.ScipShardPaths +import org.scip_code.scip_java.shared.ScipShardWriter + +/** + * Indexes Kotlin sources into SCIP documents using the Kotlin Analysis API in standalone mode. + * + * The indexer bundles its own analysis engine, so the Kotlin version of the indexed project only + * matters as a language-version setting, never as a binary-compatibility constraint. + * + * One SCIP shard is written per source file under + * `/META-INF/scip/.scip`, the layout produced by the scip-javac + * compiler plugin. + */ +class KotlinAnalysisIndexer( + private val sourceroot: Path, + private val targetroot: Path, + private val sourceRoots: List, + private val classpath: List = emptyList(), + private val jdkHome: Path? = Paths.get(System.getProperty("java.home")), + private val callback: (Document) -> Unit = {}, +) { + fun run(): List { + val disposable = Disposer.newDisposable("scip-kotlin-analysis") + try { + val session = + buildStandaloneAnalysisAPISession(projectDisposable = disposable) { + buildKtModuleProvider { + platform = JvmPlatforms.defaultJvmPlatform + val dependencies = buildList { + jdkHome?.let { home -> + add( + buildKtSdkModule { + platform = JvmPlatforms.defaultJvmPlatform + addBinaryRootsFromJdkHome(home, isJre = false) + libraryName = "JDK" + } + ) + } + if (classpath.isNotEmpty()) { + add( + buildKtLibraryModule { + platform = JvmPlatforms.defaultJvmPlatform + addBinaryRoots(classpath) + libraryName = "classpath" + } + ) + } + } + addModule( + buildKtSourceModule { + platform = JvmPlatforms.defaultJvmPlatform + moduleName = "scip-kotlin-analysis" + addSourceRoots(sourceRoots) + dependencies.forEach { addRegularDependency(it) } + } + ) + } + } + + val documents = mutableListOf() + val ktFiles = session.modulesWithFiles.values.flatten().filterIsInstance() + for (ktFile in ktFiles) { + val document = + try { + indexFile(ktFile) + } catch (e: Exception) { + val reason = e.message?.lineSequence()?.firstOrNull() + System.err.println( + "scip-kotlin-analysis: skipped ${ktFile.virtualFilePath}: ${e.javaClass.name}: $reason" + ) + continue + } + writeShard(ktFile, document) + callback(document) + documents.add(document) + } + return documents + } finally { + Disposer.dispose(disposable) + } + } + + private fun indexFile(ktFile: KtFile): Document { + val text = ktFile.text + val lines = LineIndex(text) + val builder = ScipDocumentBuilder() + analyze(ktFile) { ktFile.accept(IndexingVisitor(this, SymbolsCache(this), lines, builder)) } + val relativePath = + ScipShardPaths.relativePath(sourceroot, Paths.get(ktFile.virtualFilePath)) + return builder.build("kotlin", relativePath, text) + } + + private fun writeShard(ktFile: KtFile, document: Document) { + val absolutePath = Paths.get(ktFile.virtualFilePath).normalize() + val shardPath = ScipShardPaths.shardPath(targetroot, sourceroot, absolutePath) + if (shardPath.isPresent) { + ScipShardWriter.writeShard(shardPath.get(), document) + } else { + System.err.println( + "given file is not under the sourceroot.\n\tSourceroot: $sourceroot\n\tFile path: $absolutePath" + ) + } + } + + @OptIn(KaExperimentalApi::class) + private class IndexingVisitor( + private val session: KaSession, + private val cache: SymbolsCache, + private val lines: LineIndex, + private val out: ScipDocumentBuilder, + ) : KtTreeVisitorVoid() { + private val signatures = SignatureRenderer(session) + + override fun visitDeclaration(dcl: KtDeclaration) { + // Emit before visiting children so same-range occurrence order and + // local-symbol numbering match scip-kotlinc. + when (dcl) { + is KtEnumEntry -> emitEnumEntry(dcl) + is KtClassOrObject -> emitClassLike(dcl) + is KtConstructor<*> -> emitConstructor(dcl) + is KtNamedFunction -> emitFunction(dcl) + is KtProperty -> emitProperty(dcl) + is KtParameter -> emitParameter(dcl) + is KtPropertyAccessor -> emitExplicitAccessor(dcl) + is KtTypeParameter -> emitTypeParameter(dcl) + is KtTypeAlias -> emitTypeAlias(dcl) + is KtDestructuringDeclarationEntry -> emitDestructuringEntry(dcl) + else -> {} + } + super.visitDeclaration(dcl) + } + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + super.visitSimpleNameExpression(expression) + val referencedName = expression.getReferencedName() + if (referencedName == "this" || referencedName == "super") return + // The synthetic superclass reference of an enum entry initializer + // (`HEARTS('h')`) has no source token of its own: its "name element" + // is the whole enum class. + if (expression is KtEnumEntrySuperclassReferenceExpression) return + val target = with(session) { expression.mainReference.resolveToSymbol() } ?: return + val range = expression.getReferencedNameElement().textRange + + val targetPsi = target.psi + if ( + targetPsi is KtFunctionLiteral && + target is KaCallableSymbol && + target !is KaFunctionSymbol + ) { + emitImplicitItDefinition(targetPsi, target) + addReference(cache.implicitItSymbol(targetPsi), range) + return + } + + // Super-type entries and annotations reference the class, not the + // constructor (`class Seagull : Bird()` references `Bird#`). + if ( + target is KaConstructorSymbol && + expression.parents + .takeWhile { it !is KtFile } + .any { it is KtConstructorCalleeExpression } + ) { + addReference(cache.constructorOwnerSymbol(session, target), range) + return + } + + // Property references fan out to the property and its accessors + // (`banana` → `Class#banana.`, `Class#getBanana().`, `Class#setBanana().`). + if (target is KaPropertySymbol) { + propertyReferenceSymbols(target).forEach { addReference(it, range) } + return + } + + // A class name standing for its companion object (`Bar.instance()`) references the + // class, like scip-kotlinc; `Bar.Companion` still references the companion. + if ( + target is KaClassSymbol && + target.classKind == KaClassKind.COMPANION_OBJECT && + target.name?.asString() != referencedName + ) { + val containingClass = with(session) { target.containingSymbol } + if (containingClass != null) { + addReference(cache.symbolForReference(session, containingClass), range) + return + } + } + + addReference(cache.symbolForReference(session, target), range) + } + + private fun emitClassLike(declaration: KtClassOrObject) { + val isAnonymous = declaration is KtObjectDeclaration && declaration.isObjectLiteral() + val range = + when { + declaration.nameIdentifier != null -> declaration.nameIdentifier!!.textRange + isAnonymous -> + declaration.getObjectKeyword()?.textRange ?: declaration.textRange + // An unnamed companion object is anchored at its `companion` keyword. + else -> + declaration.modifierList?.getModifier(KtTokens.COMPANION_KEYWORD)?.textRange + ?: declaration.textRange + } + val displayName = if (isAnonymous) "" else declaration.name ?: "Companion" + val symbol = cache.symbolForDeclaration(declaration) + emitDefinition( + symbol = symbol, + range = range, + kind = + if (declaration is KtClass && declaration.isInterface()) Kind.Interface + else Kind.Class, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = displayName, + signatureText = signatures.classSignature(declaration), + documentation = docComment(declaration), + relationships = superTypeRelationships(declaration), + ) + // Classes and objects (not interfaces) get an implicit primary constructor + // when none is present. A keyword-less primary constructor also anchors at + // the class identifier; both are emitted here so the class definition stays + // first at that range (emitConstructor skips the keyword-less case). + val isInterface = declaration is KtClass && declaration.isInterface() + val primaryConstructor = declaration.primaryConstructor + if (!isInterface && primaryConstructor == null) { + emitDefinition( + symbol = cache.implicitConstructorSymbol(declaration), + range = range, + kind = Kind.Constructor, + enclosingSymbol = symbol, + enclosing = declaration.textRange, + displayName = displayName, + signatureText = signatures.implicitConstructorSignature(declaration), + documentation = docComment(declaration), + ) + } else if ( + primaryConstructor != null && primaryConstructor.getConstructorKeyword() == null + ) { + emitDefinition( + symbol = cache.symbolForDeclaration(primaryConstructor), + range = range, + kind = Kind.Constructor, + enclosingSymbol = symbol, + // Span from the definition anchor through the parameter list so the + // enclosing range contains its own anchor (scip-kotlinc's + // parameter-list-only range did not). + enclosing = + TextRange(range.startOffset, primaryConstructor.textRange.endOffset), + displayName = displayName, + signatureText = signatures.constructorSignature(primaryConstructor), + documentation = docComment(primaryConstructor), + ) + } + if (declaration is KtObjectDeclaration && !isAnonymous) { + emitObjectField(declaration, range) + } + if (declaration is KtClass && declaration.isEnum()) { + emitEnumSynthetics(declaration, range) + } + if (declaration is KtClass && declaration.isData()) { + declaration.primaryConstructor?.let { constructor -> + emitDefinition( + symbol = cache.memberMethodSymbol(declaration, "copy"), + range = constructor.textRange, + kind = Kind.Method, + enclosingSymbol = symbol, + enclosing = constructor.textRange, + displayName = "copy", + signatureText = signatures.dataCopySignature(declaration), + documentation = null, + ) + } + emitDataClassObjectMethods(declaration, range) + } + } + + /** + * The static field Java code reaches an object through: `INSTANCE` on the object itself, or + * the companion's name on its containing class. + */ + private fun emitObjectField(declaration: KtObjectDeclaration, range: TextRange) { + val owner = + if (declaration.isCompanion()) declaration.containingClassOrObject ?: return + else declaration + val name = + if (declaration.isCompanion()) declaration.name ?: "Companion" else "INSTANCE" + emitDefinition( + symbol = cache.memberTermSymbol(owner, name), + range = range, + kind = Kind.StaticField, + enclosingSymbol = cache.symbolForDeclaration(owner), + enclosing = declaration.textRange, + displayName = name, + signatureText = signatures.objectFieldSignature(name, declaration), + documentation = null, + ) + } + + /** Emit only methods Kotlin generates, not explicit or inherited final implementations. */ + private fun emitDataClassObjectMethods(declaration: KtClass, range: TextRange) { + with(session) { + val owner = declaration.symbol as? KaClassSymbol ?: return@with + val scope = owner.memberScope + for (name in listOf("equals", "hashCode", "toString")) { + for (method in + scope + .callables(Name.identifier(name)) + .filterIsInstance()) { + if (method.origin != KaSymbolOrigin.SOURCE_MEMBER_GENERATED) continue + // Use reference resolution so same-named overloads get the same numbering. + val symbol = cache.symbolForReference(session, method) + emitDefinition( + symbol = symbol, + range = range, + kind = Kind.Method, + enclosingSymbol = cache.symbolForDeclaration(declaration), + enclosing = declaration.textRange, + displayName = name, + signatureText = signatures.generatedFunctionSignature(method), + documentation = null, + relationships = + method.directlyOverriddenSymbols + .map { cache.symbolForReference(session, it) } + .toList(), + ) + for (parameter in method.valueParameters) { + emitDefinition( + symbol = + cache.methodParameterSymbol(symbol, parameter.name.asString()), + range = range, + kind = Kind.Parameter, + enclosingSymbol = symbol, + enclosing = declaration.textRange, + displayName = parameter.name.asString(), + signatureText = signatures.parameterSignature(parameter), + documentation = null, + ) + } + } + } + } + } + + /** + * Compiler-generated enum members — `values()`, `valueOf(value)`, `entries` and its getter + * — anchored at the enum class identifier. Unlike scip-kotlinc, the entries getter is owned + * by the enum class; the old package-owned symbol was a bug. + */ + private fun emitEnumSynthetics(declaration: KtClass, range: TextRange) { + val name = declaration.name.orEmpty() + val owner = cache.symbolForDeclaration(declaration) + val enclosing = declaration.textRange + emitDefinition( + symbol = cache.memberMethodSymbol(declaration, "values"), + range = range, + kind = Kind.Method, + enclosingSymbol = owner, + enclosing = enclosing, + displayName = "values", + signatureText = signatures.enumValuesSignature(name), + documentation = null, + ) + val valueOf = cache.memberMethodSymbol(declaration, "valueOf") + emitDefinition( + symbol = valueOf, + range = range, + kind = Kind.Method, + enclosingSymbol = owner, + enclosing = enclosing, + displayName = "valueOf", + signatureText = signatures.enumValueOfSignature(name), + documentation = null, + ) + emitDefinition( + symbol = cache.methodParameterSymbol(valueOf, "value"), + range = range, + kind = Kind.Parameter, + enclosingSymbol = valueOf, + enclosing = enclosing, + displayName = "value", + signatureText = "value: String", + documentation = null, + ) + val entries = cache.memberTermSymbol(declaration, "entries") + emitDefinition( + symbol = entries, + range = range, + kind = Kind.Property, + enclosingSymbol = owner, + enclosing = enclosing, + displayName = "entries", + signatureText = signatures.enumEntriesSignature(name), + documentation = null, + ) + emitDefinition( + symbol = cache.memberMethodSymbol(declaration, "getEntries"), + range = range, + kind = Kind.Method, + enclosingSymbol = entries, + enclosing = enclosing, + displayName = "entries", + signatureText = signatures.enumGetEntriesSignature(name), + documentation = null, + ) + } + + private fun emitEnumEntry(declaration: KtEnumEntry) { + val identifier = declaration.nameIdentifier ?: return + val symbol = cache.symbolForDeclaration(declaration) + emitDefinition( + symbol = symbol, + range = identifier.textRange, + kind = Kind.EnumMember, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.enumEntrySignature(declaration), + documentation = docComment(declaration), + ) + if (declaration.body != null) emitEnumEntryBody(declaration, symbol, identifier) + } + + /** + * An enum entry body declares an anonymous subclass of the enum, as in FIR: a local class + * and its constructor, both anchored at the entry identifier. + */ + private fun emitEnumEntryBody( + declaration: KtEnumEntry, + entry: Symbol, + identifier: PsiElement, + ) { + val enumClass = declaration.containingClassOrObject ?: return + val anonymousClass = cache.enumEntryBodySymbol(declaration) + emitDefinition( + symbol = anonymousClass, + range = identifier.textRange, + kind = Kind.Class, + enclosingSymbol = entry, + enclosing = declaration.textRange, + displayName = "", + signatureText = "object : ${enumClass.name.orEmpty()}", + documentation = null, + relationships = listOf(cache.symbolForDeclaration(enumClass)), + ) + emitDefinition( + symbol = cache.enumEntryBodyConstructorSymbol(declaration), + range = identifier.textRange, + kind = Kind.Constructor, + enclosingSymbol = anonymousClass, + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = "private constructor(): ", + documentation = null, + ) + } + + private fun emitConstructor(declaration: KtConstructor<*>) { + val range = + when (declaration) { + // Keyword-less primary constructors are emitted by emitClassLike. + is KtPrimaryConstructor -> + declaration.getConstructorKeyword()?.textRange ?: return + is KtSecondaryConstructor -> declaration.textRange + else -> return + } + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = range, + kind = Kind.Constructor, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.containingClassOrObject?.name ?: "", + signatureText = signatures.constructorSignature(declaration), + documentation = docComment(declaration), + ) + } + + private fun emitFunction(declaration: KtNamedFunction) { + val identifier = declaration.nameIdentifier ?: return + val relationships = + if (declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) { + overriddenSymbols(declaration) + } else { + emptyList() + } + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = identifier.textRange, + kind = Kind.Method, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.functionSignature(declaration), + documentation = docComment(declaration), + relationships = relationships, + ) + } + + private fun emitProperty(declaration: KtProperty) { + val identifier = declaration.nameIdentifier ?: return + val symbol = cache.symbolForDeclaration(declaration) + if (symbol.isLocal()) { + emitDefinition( + symbol = symbol, + range = identifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.localVariableSignature(declaration), + documentation = docComment(declaration), + ) + return + } + emitDefinition( + symbol = symbol, + range = identifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.propertySignature(declaration), + documentation = docComment(declaration), + ) + emitSyntheticAccessors( + property = declaration, + nameRange = identifier.textRange, + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + hasExplicitGetter = declaration.getter != null, + hasExplicitSetter = declaration.setter != null, + isVar = declaration.isVar, + ) + } + + private fun emitParameter(declaration: KtParameter) { + val loopOrCatchIdentifier = declaration.nameIdentifier + // `for` loop variables and `catch` parameters are local declarations, + // numbered at the declaration site like in the FIR indexer. + if (loopOrCatchIdentifier != null) { + if (declaration.parent is KtForExpression) { + val type = + declaration.typeReference?.text + ?: renderedParameterType(declaration) + ?: "Any" + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = loopOrCatchIdentifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = "local val ${declaration.name.orEmpty()}: $type", + documentation = null, + ) + return + } + if ((declaration.parent as? KtParameterList)?.parent is KtCatchClause) { + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = loopOrCatchIdentifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.parameterSignature(declaration), + documentation = null, + ) + return + } + } + // Parameters of function types are not declarations of their own. + if (!cache.isDeclarationParameter(declaration)) return + val identifier = declaration.nameIdentifier ?: return + val symbol = cache.symbolForDeclaration(declaration) + emitDefinition( + symbol = symbol, + range = identifier.textRange, + kind = Kind.Parameter, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.parameterSignature(declaration), + documentation = null, + ) + // A `val`/`var` constructor parameter also declares a property; it and + // its synthetic accessors are all anchored at the parameter identifier, + // which the property initializer also references. + if (declaration.hasValOrVar()) { + emitDefinition( + symbol = cache.parameterPropertySymbol(declaration), + range = identifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration.ownerFunction ?: declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.propertySignature(declaration), + documentation = null, + ) + addReference(symbol, identifier.textRange) + emitSyntheticAccessors( + property = declaration, + nameRange = identifier.textRange, + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + hasExplicitGetter = false, + hasExplicitSetter = false, + isVar = declaration.isMutable, + ) + emitDataClassParameterSynthetics(declaration, identifier.textRange) + } + } + + /** + * A data-class constructor parameter additionally declares `componentN()` and a `copy()` + * parameter, and the generated `copy()` default value reads the property — all anchored at + * the parameter identifier, mirroring scip-kotlinc. + */ + private fun emitDataClassParameterSynthetics(declaration: KtParameter, range: TextRange) { + val constructor = declaration.parent?.parent as? KtPrimaryConstructor ?: return + val containingClass = constructor.containingClassOrObject as? KtClass ?: return + if (!containingClass.isData()) return + val index = constructor.valueParameters.indexOf(declaration) + if (index < 0) return + emitDefinition( + symbol = cache.memberMethodSymbol(containingClass, "component${index + 1}"), + range = range, + kind = Kind.Method, + enclosingSymbol = cache.symbolForDeclaration(containingClass), + enclosing = declaration.textRange, + displayName = "component${index + 1}", + signatureText = signatures.dataComponentSignature(index + 1, declaration), + documentation = null, + ) + val copy = cache.memberMethodSymbol(containingClass, "copy") + emitDefinition( + symbol = cache.methodParameterSymbol(copy, declaration.name.orEmpty()), + range = range, + kind = Kind.Parameter, + enclosingSymbol = copy, + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.dataCopyParameterSignature(declaration), + documentation = null, + ) + addReference(cache.parameterPropertySymbol(declaration), range) + addReference(cache.syntheticAccessorSymbol(declaration, setter = false), range) + if (declaration.isMutable) { + addReference(cache.syntheticAccessorSymbol(declaration, setter = true), range) + } + } + + private fun emitTypeAlias(declaration: KtTypeAlias) { + val identifier = declaration.nameIdentifier ?: return + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = identifier.textRange, + kind = Kind.TypeAlias, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.typeAliasSignature(declaration), + documentation = docComment(declaration), + ) + } + + /** + * `when (subject)` introduces a synthetic `` local in the FIR desugaring, + * defined at the subject expression. Emitted before visiting children so local numbering + * matches. + */ + override fun visitWhenExpression(expression: KtWhenExpression) { + val subject = expression.subjectExpression + if (subject != null && expression.subjectVariable == null) { + emitDefinition( + symbol = cache.syntheticLocalSymbol(expression), + range = subject.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(expression), + enclosing = subject.textRange, + displayName = "", + signatureText = + signatures.whenSubjectSignature(renderedExpressionType(subject)), + documentation = null, + ) + } + super.visitWhenExpression(expression) + } + + /** + * Destructuring introduces a synthetic `` local holding the destructured value; + * each entry references its `componentN()` function and the `` local. Emitted + * before visiting children so local numbering matches. + */ + override fun visitDestructuringDeclaration( + destructuringDeclaration: KtDestructuringDeclaration + ) { + val isParameter = destructuringDeclaration.parent is KtParameter + val type = + destructuringDeclaration.initializer?.let { renderedExpressionType(it) } + ?: (destructuringDeclaration.parent as? KtParameter)?.let { + renderedParameterType(it) + } + ?: "Any" + emitDefinition( + symbol = cache.syntheticLocalSymbol(destructuringDeclaration), + range = destructuringDeclaration.textRange, + kind = if (isParameter) Kind.Parameter else Kind.Property, + enclosingSymbol = enclosingSymbol(destructuringDeclaration), + enclosing = destructuringDeclaration.textRange, + displayName = "", + signatureText = signatures.destructSignature(type, isParameter), + documentation = null, + ) + super.visitDestructuringDeclaration(destructuringDeclaration) + } + + private fun emitDestructuringEntry(declaration: KtDestructuringDeclarationEntry) { + emitLocalVariable(declaration) + val range = declaration.nameIdentifier?.textRange ?: return + val destructuring = declaration.parent as? KtDestructuringDeclaration ?: return + // Entries desugar to `componentN()` calls on the destructured value. + // Standalone analysis does not resolve destructuring-entry references, so + // the symbol is derived from the destructured type and entry position. + val index = destructuring.entries.indexOf(declaration) + destructuredClassId(destructuring)?.let { classId -> + val name = "component${index + 1}" + // `componentN` may be a member (data classes, Pair) or an extension + // (e.g. Map.Entry's componentN lives in kotlin.collections). + val isMember = + with(session) { + findClass(classId) + ?.declaredMemberScope + ?.callables(Name.identifier(name)) + ?.any() == true + } + val symbol = + if (isMember) { + Symbol.createGlobal( + cache.classSymbol(classId), + ScipSymbolDescriptor(ScipSymbolDescriptor.Kind.METHOD, name), + ) + } else { + with(session) { + findTopLevelCallables(classId.packageFqName, Name.identifier(name)) + .firstOrNull { extension -> + (extension.receiverParameter?.returnType as? KaClassType) + ?.classId == classId + } + ?.let { cache.symbolForReference(session, it) } + } ?: Symbol.NONE + } + addReference(symbol, range) + } + addReference(cache.syntheticLocalSymbol(destructuring), range) + } + + private fun destructuredClassId(destructuring: KtDestructuringDeclaration): ClassId? = + with(session) { + val type = + destructuring.initializer?.expressionType + ?: (destructuring.parent as? KtParameter)?.let { + (it.symbol as? KaCallableSymbol)?.returnType + } + (type as? KaClassType)?.classId + } + + private fun renderedExpressionType(expression: KtExpression): String = + with(session) { + expression.expressionType?.render( + KaTypeRendererForSource.WITH_SHORT_NAMES, + position = Variance.INVARIANT, + ) + } ?: "Any" + + private fun renderedParameterType(parameter: KtParameter): String? = + with(session) { + (parameter.symbol as? KaCallableSymbol) + ?.returnType + ?.render( + KaTypeRendererForSource.WITH_SHORT_NAMES, + position = Variance.INVARIANT, + ) + } + + private fun emitSyntheticAccessors( + property: KtDeclaration, + nameRange: TextRange, + enclosing: TextRange, + displayName: String, + hasExplicitGetter: Boolean, + hasExplicitSetter: Boolean, + isVar: Boolean, + ) { + val propertySymbol = + if (property is KtParameter) cache.parameterPropertySymbol(property) + else cache.symbolForDeclaration(property) + if (!hasExplicitGetter) { + emitDefinition( + symbol = cache.syntheticAccessorSymbol(property, setter = false), + range = nameRange, + kind = Kind.Method, + enclosingSymbol = propertySymbol, + enclosing = enclosing, + displayName = displayName, + signatureText = signatures.syntheticAccessorSignature(property, setter = false), + documentation = null, + ) + } + if (isVar && !hasExplicitSetter) { + val setter = cache.syntheticAccessorSymbol(property, setter = true) + emitDefinition( + symbol = setter, + range = nameRange, + kind = Kind.Method, + enclosingSymbol = propertySymbol, + enclosing = enclosing, + displayName = displayName, + signatureText = signatures.syntheticAccessorSignature(property, setter = true), + documentation = null, + ) + emitDefinition( + symbol = cache.syntheticSetterValueSymbol(property), + range = nameRange, + kind = Kind.Parameter, + enclosingSymbol = setter, + enclosing = enclosing, + displayName = "value", + signatureText = signatures.setterValueSignature(property), + documentation = null, + ) + } + } + + private fun emitExplicitAccessor(declaration: KtPropertyAccessor) { + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = declaration.namePlaceholder.textRange, + kind = Kind.Method, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.property.name.orEmpty(), + signatureText = signatures.accessorSignature(declaration), + documentation = docComment(declaration), + ) + } + + private fun emitTypeParameter(declaration: KtTypeParameter) { + val identifier = declaration.nameIdentifier ?: return + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = identifier.textRange, + kind = Kind.TypeParameter, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = declaration.text, + documentation = null, + ) + } + + private fun emitLocalVariable(declaration: KtVariableDeclaration) { + val identifier = declaration.nameIdentifier ?: return + emitDefinition( + symbol = cache.symbolForDeclaration(declaration), + range = identifier.textRange, + kind = Kind.Property, + enclosingSymbol = enclosingSymbol(declaration), + enclosing = declaration.textRange, + displayName = declaration.name.orEmpty(), + signatureText = signatures.localVariableSignature(declaration), + documentation = null, + ) + } + + private val emittedItParameters = HashSet() + + /** + * The implicit `it` parameter has no declaration PSI: its definition is anchored at the + * whole lambda literal and emitted at the first reference. + */ + private fun emitImplicitItDefinition(literal: KtFunctionLiteral, target: KaCallableSymbol) { + if (!emittedItParameters.add(literal)) return + emitDefinition( + symbol = cache.implicitItSymbol(literal), + range = literal.textRange, + kind = Kind.Parameter, + enclosingSymbol = Symbol.NONE, + enclosing = literal.textRange, + displayName = "it", + signatureText = signatures.implicitItSignature(target), + documentation = null, + ) + } + + private fun propertyReferenceSymbols(target: KaPropertySymbol): List { + return when (val psi = target.psi) { + is KtProperty -> { + val property = cache.symbolForDeclaration(psi) + if (property.isLocal()) return listOf(property) + buildList { + add(property) + psi.getter?.let { add(cache.symbolForDeclaration(it)) } + ?: add(cache.syntheticAccessorSymbol(psi, setter = false)) + if (psi.isVar) { + psi.setter?.let { add(cache.symbolForDeclaration(it)) } + ?: add(cache.syntheticAccessorSymbol(psi, setter = true)) + } + } + } + is KtParameter -> + buildList { + add(cache.parameterPropertySymbol(psi)) + add(cache.syntheticAccessorSymbol(psi, setter = false)) + if (psi.isMutable) { + add(cache.syntheticAccessorSymbol(psi, setter = true)) + } + } + else -> + buildList { + add(cache.symbolForReference(session, target)) + add(cache.externalAccessorSymbol(session, target, setter = false)) + if (!target.isVal) { + add(cache.externalAccessorSymbol(session, target, setter = true)) + } + } + }.filter { it != Symbol.NONE } + } + + private fun superTypeRelationships(declaration: KtClassOrObject): List = + with(session) { + val classSymbol = declaration.symbol as? KaClassSymbol ?: return@with emptyList() + classSymbol.superTypes + .mapNotNull { (it as? KaClassType)?.classId } + .filterNot { it == ANY_CLASS_ID } + .map { cache.classSymbol(it) } + .filter { it != Symbol.NONE } + } + + private fun overriddenSymbols(declaration: KtNamedFunction): List = + with(session) { + val symbol = declaration.symbol as? KaCallableSymbol ?: return@with emptyList() + symbol.directlyOverriddenSymbols + .map { cache.symbolForReference(session, it) } + .filter { it != Symbol.NONE } + .toList() + } + + private fun emitDefinition( + symbol: Symbol, + range: TextRange, + kind: Kind, + enclosingSymbol: Symbol, + enclosing: TextRange?, + displayName: String, + signatureText: String?, + documentation: String?, + relationships: List = emptyList(), + ) { + if (symbol == Symbol.NONE) return + val scipRange = lines.anchorRange(range) ?: return + val builder = + Occurrence.newBuilder() + .setSymbol(symbol.toString()) + .setSymbolRoles(SymbolRole.Definition.number) + builder.setSingleLineRange(scipRange.toSingleLineRange()) + enclosing + ?.let { lines.scipRange(it) } + ?.let { enclosingRange -> + if (enclosingRange.isSingleLine) { + builder.setSingleLineEnclosingRange(enclosingRange.toSingleLineRange()) + } else { + builder.setMultiLineEnclosingRange(enclosingRange.toMultiLineRange()) + } + } + out.addOccurrence(builder.build()) + out.addSymbol( + symbolInformation { + this.symbol = symbol.toString() + this.kind = kind + this.enclosingSymbol = enclosingSymbol.toString() + this.displayName = displayName + if (signatureText != null) { + this.signatureDocumentation = signature { + language = "kotlin" + text = signatureText + } + } + if (documentation != null) { + this.documentation += documentation + } + for (parent in relationships) { + this.relationships += relationship { + this.symbol = parent.toString() + this.isImplementation = true + } + } + } + ) + } + + /** + * The symbol of the nearest declaration containing [element], or the package for top-level + * declarations. Lambdas have no symbol, so declarations inside them have none either. + */ + private fun enclosingSymbol(element: PsiElement): Symbol { + for (parent in element.parents) { + when (parent) { + is KtFunctionLiteral -> return Symbol.NONE + is KtEnumEntry -> return cache.enumEntryBodySymbol(parent) + is KtClassOrObject, + is KtNamedFunction, + is KtConstructor<*>, + is KtProperty, + is KtPropertyAccessor -> + return cache.symbolForDeclaration(parent as KtDeclaration) + is KtFile -> return cache.packageSymbol(parent.packageFqName) + } + } + return Symbol.NONE + } + + private fun addReference(symbol: Symbol, range: TextRange) { + if (symbol == Symbol.NONE) return + val scipRange = lines.anchorRange(range) ?: return + val builder = Occurrence.newBuilder().setSymbol(symbol.toString()) + builder.setSingleLineRange(scipRange.toSingleLineRange()) + out.addOccurrence(builder.build()) + } + + private fun docComment(declaration: KtDeclaration): String? { + val kdoc = declaration.docComment?.text ?: return null + return stripKdoc(kdoc).ifEmpty { null } + } + + companion object { + private val ANY_CLASS_ID = ClassId(FqName("kotlin"), Name.identifier("Any")) + } + } +} + +/** Strips the `/**`, leading `*`s, and `*/` from a kdoc block, returning just the body text. */ +internal fun stripKdoc(kdoc: String): String { + if (kdoc.isEmpty()) return kdoc + val out = StringBuilder() + var first = true + kdoc.lineSequence().forEach { line -> + if (line.isEmpty()) return@forEach + var start = 0 + while (start < line.length && line[start].isWhitespace()) start++ + if (start < line.length && line[start] == '/') start++ + while (start < line.length && line[start] == '*') start++ + var end = line.length - 1 + if (end > start && line[end] == '/') end-- + while (end > start && line[end] == '*') end-- + while (end > start && line[end].isWhitespace()) end-- + start = minOf(start, line.length - 1) + if (end > start) end++ + if (!first) out.append('\n') + out.append(line, start, end) + first = false + } + return out.toString().trim() +} + +/** Maps text offsets to 0-based line/character positions. */ +internal class LineIndex(private val text: String) { + private val lineStartOffsets: IntArray + + init { + val offsets = ArrayList() + offsets.add(0) + for (index in text.indices) { + if (text[index] == '\n') offsets.add(index + 1) + } + lineStartOffsets = offsets.toIntArray() + } + + fun scipRange(range: TextRange): ScipRange? { + if (range.startOffset > text.length || range.endOffset > text.length) return null + val (startLine, startCharacter) = position(range.startOffset) + val (endLine, endCharacter) = position(range.endOffset) + return ScipRange.range(startLine, startCharacter, endLine, endCharacter) + } + + /** + * Occurrence anchors are always single-line: multiline declarations are collapsed onto their + * start line, clamped to that line's end so the range stays within the source. The true extent + * lives in the enclosing range. + */ + fun anchorRange(range: TextRange): ScipRange? { + if (range.startOffset > text.length || range.endOffset > text.length) return null + val (startLine, startCharacter) = position(range.startOffset) + val (endLine, endCharacter) = position(range.endOffset) + if (startLine == endLine) { + return ScipRange.singleLine(startLine, startCharacter, endCharacter) + } + return ScipRange.singleLine(startLine, startCharacter, lineLength(startLine)) + } + + private fun lineStart(line: Int): Int = lineStartOffsets[line] + + /** Length of [line] excluding its trailing newline. */ + private fun lineLength(line: Int): Int { + val end = + if (line + 1 < lineStartOffsets.size) lineStartOffsets[line + 1] - 1 else text.length + return end - lineStart(line) + } + + private fun position(offset: Int): Pair { + var line = Arrays.binarySearch(lineStartOffsets, offset) + if (line < 0) line = -line - 2 + return line to (offset - lineStartOffsets[line]) + } +} diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt new file mode 100644 index 000000000..5e6f41170 --- /dev/null +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/Main.kt @@ -0,0 +1,51 @@ +package org.scip_code.scip_java.kotlin_analysis + +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.system.exitProcess + +/** + * Command-line entry point, primarily used by the scip-snapshots harness. The scip-java CLI calls + * [KotlinAnalysisIndexer] in-process instead. + * + * Usage: `--sourceroot --targetroot [--classpath ] [--jdk-home + * ] ...` + */ +fun main(args: Array) { + var sourceroot: Path? = null + var targetroot: Path? = null + var classpath: List = emptyList() + var jdkHome: Path? = Paths.get(System.getProperty("java.home")) + val sourceRoots = mutableListOf() + var i = 0 + while (i < args.size) { + when (val arg = args[i]) { + "--sourceroot" -> sourceroot = Paths.get(args[++i]) + "--targetroot" -> targetroot = Paths.get(args[++i]) + "--classpath" -> + classpath = + args[++i] + .split(File.pathSeparator) + .filter { it.isNotEmpty() } + .map { Paths.get(it) } + "--jdk-home" -> jdkHome = Paths.get(args[++i]) + else -> sourceRoots.add(Paths.get(arg)) + } + i++ + } + require(sourceroot != null) { "missing required flag: --sourceroot" } + require(targetroot != null) { "missing required flag: --targetroot" } + require(sourceRoots.isNotEmpty()) { "missing source files or directories" } + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot, + sourceRoots = sourceRoots, + classpath = classpath, + jdkHome = jdkHome, + ) + .run() + // The Analysis API environment leaves non-daemon threads behind; exit + // explicitly so callers are not left waiting on them. + exitProcess(0) +} diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipSymbols.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/ScipSymbols.kt similarity index 76% rename from scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipSymbols.kt rename to scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/ScipSymbols.kt index b3ffb4520..2ed76e9bd 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipSymbols.kt +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/ScipSymbols.kt @@ -1,4 +1,4 @@ -package org.scip_code.scip_java.kotlinc +package org.scip_code.scip_java.kotlin_analysis import org.scip_code.scip_java.shared.ScipSymbols as SharedSymbols @@ -8,10 +8,8 @@ value class Symbol(private val symbol: String) { val NONE = Symbol(SharedSymbols.NONE) val ROOT_PACKAGE = Symbol(SharedSymbols.ROOT_PACKAGE) - // Note: this intentionally diverges from `SharedSymbols.global` when - // `desc == NONE` — Java returns `NONE`, Kotlin returns the owner. - // SymbolsCache relies on this behavior; do not delegate without first - // updating those call sites. + // Diverges from `SharedSymbols.global` when `desc == NONE`: that returns + // NONE, this returns the owner — SymbolsCache relies on it. fun createGlobal(owner: Symbol, desc: ScipSymbolDescriptor): Symbol = when { desc == ScipSymbolDescriptor.NONE -> owner @@ -29,14 +27,11 @@ value class Symbol(private val symbol: String) { override fun toString(): String = symbol } -fun String.symbol(): Symbol = Symbol(this) - data class ScipSymbolDescriptor( val kind: Kind, val name: String, - // Default differs from `SharedSymbols.Descriptor` (which is "") because - // Kotlin call sites — getters/setters in particular — rely on the no-arg - // overload producing `name().` rather than `name.` for METHOD kinds. + // Defaults to "()" (`SharedSymbols.Descriptor` defaults to ""): call sites + // rely on the no-arg form producing `name().` for METHOD kinds. val disambiguator: String = "()", ) { companion object { diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SignatureRenderer.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SignatureRenderer.kt new file mode 100644 index 000000000..a703da574 --- /dev/null +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SignatureRenderer.kt @@ -0,0 +1,334 @@ +package org.scip_code.scip_java.kotlin_analysis + +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource +import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPropertyAccessor +import org.jetbrains.kotlin.psi.KtTypeAlias +import org.jetbrains.kotlin.psi.KtVariableDeclaration +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.types.Variance + +/** + * Renders hover signatures in the textual format of scip-kotlinc's FirRenderer setup (see the + * goldens under scip-snapshots/expected/kotlin). Explicit type annotations are taken verbatim from + * source; inferred types are rendered with short names via the Analysis API. + */ +@OptIn(KaExperimentalApi::class) +internal class SignatureRenderer(private val session: KaSession) { + + fun classSignature(declaration: KtClassOrObject): String { + if (declaration is KtObjectDeclaration && declaration.isObjectLiteral()) { + return "object : ${superTypesText(declaration)}" + } + val keyword = + when { + declaration is KtObjectDeclaration && declaration.isCompanion() -> + "companion object" + declaration is KtObjectDeclaration -> "object" + declaration is KtClass && declaration.isInterface() -> "interface" + declaration is KtClass && declaration.isEnum() -> "enum class" + declaration is KtClass && declaration.isAnnotation() -> "annotation class" + declaration is KtClass && declaration.isData() -> "data class" + declaration is KtClass && declaration.isInner() -> "inner class" + else -> "class" + } + val name = declaration.name ?: "Companion" + val typeParameters = (declaration as? KtClass)?.typeParameterList?.text.orEmpty() + return "${annotationsPrefix(declaration)}${visibility(declaration)} " + + "${classModality(declaration)} $keyword $name$typeParameters : " + + superTypesText(declaration) + } + + fun constructorSignature(constructor: KtConstructor<*>): String { + val owner = constructor.containingClassOrObject + val visibility = explicitVisibility(constructor) ?: constructorVisibility(owner) + return "$visibility constructor${classTypeParameters(owner)}" + + "(${parametersText(constructor.valueParameters)}): " + + constructorReturnType(owner) + } + + fun implicitConstructorSignature(declaration: KtClassOrObject): String { + return "${constructorVisibility(declaration)} constructor(): ${ownerName(declaration)}" + } + + fun functionSignature(declaration: KtNamedFunction): String { + val returnType = + declaration.typeReference?.text ?: renderedReturnType(declaration) ?: "Unit" + val typeParameters = declaration.typeParameterList?.text?.let { "$it " }.orEmpty() + val signature = + "${annotationsPrefix(declaration)}${visibility(declaration)} " + + "${memberModality(declaration)}fun $typeParameters${receiverPrefix(declaration)}" + + "${declaration.name.orEmpty()}(${parametersText(declaration.valueParameters)}): " + + returnType + // FirRenderer emitted a trailing newline for functions without a body + // (see the Animal#sound() golden). + return if (declaration.hasBody()) signature else signature + "\n" + } + + fun typeAliasSignature(declaration: KtTypeAlias): String = + "public final typealias ${declaration.name.orEmpty()} = " + + declaration.getTypeReference()?.text.orEmpty() + + "\n" + + fun enumValuesSignature(name: String): String = "public final static fun values(): Array<$name>" + + fun enumValueOfSignature(name: String): String = + "public final static fun valueOf(value: String): $name" + + fun enumEntriesSignature(name: String): String = + "public final static val entries: EnumEntries<$name>" + + fun enumGetEntriesSignature(name: String): String = "public static get(): EnumEntries<$name>" + + fun objectFieldSignature(name: String, declaration: KtObjectDeclaration): String = + "public final static val $name: ${ownerName(declaration)}" + + fun enumEntrySignature(entry: KtEnumEntry): String = + "${annotationsPrefix(entry)}public final static enum entry ${entry.name.orEmpty()}: " + + ownerName(entry.containingClassOrObject) + + fun dataCopySignature(declaration: KtClass): String { + val parameters = + declaration.primaryConstructor?.valueParameters.orEmpty().joinToString(", ") { + "${it.name.orEmpty()}: ${it.typeReference?.text ?: "Any"} = ..." + } + return "public final fun copy($parameters): ${ownerName(declaration)}\n" + } + + fun dataComponentSignature(index: Int, parameter: KtParameter): String = + "public final operator fun component$index(): ${parameter.typeReference?.text ?: "Any"}\n" + + fun dataCopyParameterSignature(parameter: KtParameter): String = + "${parameter.name.orEmpty()}: ${parameter.typeReference?.text ?: "Any"} = ..." + + fun generatedFunctionSignature(function: KaNamedFunctionSymbol): String = + with(session) { + val parameters = function.valueParameters.joinToString(", ") { parameterSignature(it) } + val returnType = + function.returnType.render( + KaTypeRendererForSource.WITH_SHORT_NAMES, + position = Variance.INVARIANT, + ) + val operator = if (function.isOperator) "operator " else "" + return "public ${function.modality.name.lowercase()} override ${operator}fun ${function.name}($parameters): $returnType" + } + + fun parameterSignature(parameter: KaValueParameterSymbol): String = + with(session) { + "${parameter.name}: ${parameter.returnType.render(KaTypeRendererForSource.WITH_SHORT_NAMES, position = Variance.INVARIANT)}" + } + + fun whenSubjectSignature(type: String): String = "local val : $type" + + fun destructSignature(type: String, isParameter: Boolean): String = + if (isParameter) ": $type" else "local val : $type" + + private fun constructorVisibility(declaration: KtClassOrObject?): String = + when { + declaration is KtObjectDeclaration -> "private" + declaration is KtClass && declaration.isEnum() -> "private" + else -> "public" + } + + private fun classTypeParameters(declaration: KtClassOrObject?): String = + (declaration as? KtClass)?.typeParameterList?.text.orEmpty() + + private fun constructorReturnType(declaration: KtClassOrObject?): String { + val name = ownerName(declaration) + val typeArguments = + (declaration as? KtClass)?.typeParameters?.mapNotNull { it.name }.orEmpty() + return if (typeArguments.isEmpty()) name else "$name<${typeArguments.joinToString(", ")}>" + } + + private fun annotationsPrefix(declaration: KtDeclaration): String = + declaration.annotationEntries.joinToString("") { entry -> + val name = entry.shortName?.asString().orEmpty() + val useSite = entry.useSiteTarget?.let { "${it.text}:" }.orEmpty() + val arguments = if (entry.valueArguments.isEmpty()) "()" else "(...)" + "$useSite@$name$arguments " + } + + fun propertySignature(declaration: KtDeclaration): String { + val name = (declaration as? KtNamedDeclaration)?.name.orEmpty() + val const = if (declaration.hasModifier(KtTokens.CONST_KEYWORD)) "const " else "" + val lateinit = if (declaration.hasModifier(KtTokens.LATEINIT_KEYWORD)) "lateinit " else "" + val valOrVar = if (isVar(declaration)) "var" else "val" + val type = explicitPropertyType(declaration) ?: renderedReturnType(declaration) ?: "Any" + val receiver = (declaration as? KtProperty)?.let(::receiverPrefix).orEmpty() + return "${visibility(declaration)} ${memberModality(declaration)}$const$lateinit$valOrVar " + + "$receiver$name: $type" + } + + fun localVariableSignature(declaration: KtVariableDeclaration): String { + val valOrVar = if (declaration.isVar) "var" else "val" + val type = declaration.typeReference?.text ?: renderedReturnType(declaration) ?: "Any" + return "${annotationsPrefix(declaration)}local $valOrVar ${declaration.name.orEmpty()}: $type" + } + + fun accessorSignature(accessor: KtPropertyAccessor): String { + val property = accessor.property + val visibility = explicitVisibility(accessor) ?: visibility(property) + val type = explicitPropertyType(property) ?: renderedReturnType(property) ?: "Any" + return if (accessor.isSetter) { + val parameterName = accessor.parameter?.name ?: "value" + "$visibility set($parameterName: $type): Unit" + } else { + "$visibility get(): $type" + } + } + + fun syntheticAccessorSignature(property: KtDeclaration, setter: Boolean): String { + val visibility = visibility(property) + val type = explicitPropertyType(property) ?: renderedReturnType(property) ?: "Any" + return if (setter) "$visibility set(value: $type): Unit" else "$visibility get(): $type" + } + + fun setterValueSignature(property: KtDeclaration): String { + val type = explicitPropertyType(property) ?: renderedReturnType(property) ?: "Any" + return "value: $type" + } + + fun parameterSignature(parameter: KtParameter): String { + val vararg = if (parameter.hasModifier(KtTokens.VARARG_KEYWORD)) "vararg " else "" + val type = parameter.typeReference?.text ?: renderedReturnType(parameter) ?: "Any" + val default = if (parameter.hasDefaultValue()) " = ..." else "" + return "$vararg${parameter.name.orEmpty()}: $type$default" + } + + fun implicitItSignature(target: KaCallableSymbol): String { + val type = + with(session) { + target.returnType.render( + KaTypeRendererForSource.WITH_SHORT_NAMES, + position = Variance.INVARIANT, + ) + } + return "it: $type" + } + + private fun ownerName(declaration: KtClassOrObject?): String { + if (declaration == null) return "" + if (declaration is KtObjectDeclaration && declaration.isObjectLiteral()) { + return "" + } + val names = mutableListOf() + var current: KtClassOrObject? = declaration + while (current != null) { + names.add(current.name ?: "Companion") + current = current.containingClassOrObject + } + return names.reversed().joinToString(".") + } + + private fun superTypesText(declaration: KtClassOrObject): String { + val entries = declaration.superTypeListEntries.mapNotNull { it.typeReference?.text } + if (entries.isNotEmpty()) return entries.joinToString(", ") + return when { + declaration is KtClass && declaration.isEnum() -> "Enum<${declaration.name.orEmpty()}>" + declaration is KtClass && declaration.isAnnotation() -> "Annotation" + else -> "Any" + } + } + + private fun parametersText(parameters: List): String = + parameters.joinToString(", ") { parameter -> + val type = parameter.typeReference?.text ?: "Any" + val default = if (parameter.hasDefaultValue()) " = ..." else "" + "${parameter.name.orEmpty()}: $type$default" + } + + private fun explicitPropertyType(declaration: KtDeclaration): String? = + when (declaration) { + is KtProperty -> declaration.typeReference?.text + is KtParameter -> declaration.typeReference?.text + else -> null + } + + private fun renderedReturnType(declaration: KtDeclaration): String? = + with(session) { + (declaration.symbol as? KaCallableSymbol) + ?.returnType + ?.render(KaTypeRendererForSource.WITH_SHORT_NAMES, position = Variance.INVARIANT) + } + + private fun isVar(declaration: KtDeclaration): Boolean = + when (declaration) { + is KtProperty -> declaration.isVar + is KtParameter -> declaration.isMutable + else -> false + } + + /** + * Functions and classes declared in a code block render as `local`, like FirRenderer; members + * of local classes do not. + */ + private fun visibility(declaration: KtDeclaration): String = + explicitVisibility(declaration) + ?: if (declaration.parent is KtBlockExpression) "local" else "public" + + /** The extension receiver of [declaration] with a trailing dot (`String.`), if any. */ + private fun receiverPrefix(declaration: KtCallableDeclaration): String = + declaration.receiverTypeReference?.let { "${it.text}." }.orEmpty() + + private fun explicitVisibility(declaration: KtDeclaration): String? = + when { + declaration.hasModifier(KtTokens.PRIVATE_KEYWORD) -> "private" + declaration.hasModifier(KtTokens.PROTECTED_KEYWORD) -> "protected" + declaration.hasModifier(KtTokens.INTERNAL_KEYWORD) -> "internal" + else -> null + } + + private fun classModality(declaration: KtClassOrObject): String = + when { + declaration is KtClass && declaration.isInterface() -> "abstract" + declaration.hasModifier(KtTokens.SEALED_KEYWORD) -> "sealed" + declaration.hasModifier(KtTokens.ABSTRACT_KEYWORD) -> "abstract" + declaration.hasModifier(KtTokens.OPEN_KEYWORD) -> "open" + else -> "final" + } + + /** Modality plus `override`, with a trailing space. */ + private fun memberModality(declaration: KtDeclaration): String { + val containingInterface = + (declaration.containingClassOrObject as? KtClass)?.isInterface() == true + val isAbstract = + declaration.hasModifier(KtTokens.ABSTRACT_KEYWORD) || + (containingInterface && !hasImplementation(declaration)) + val isOverride = declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD) + val modality = + when { + isAbstract -> "abstract" + declaration.hasModifier(KtTokens.OPEN_KEYWORD) || isOverride -> "open" + containingInterface -> "open" + else -> "final" + } + return if (isOverride) "$modality override " else "$modality " + } + + private fun hasImplementation(declaration: KtDeclaration): Boolean = + when (declaration) { + is KtNamedFunction -> declaration.hasBody() + is KtProperty -> + declaration.hasInitializer() || + declaration.hasDelegate() || + declaration.accessors.any { it.hasBody() } + else -> true + } +} diff --git a/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SymbolsCache.kt b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SymbolsCache.kt new file mode 100644 index 000000000..6952f23a7 --- /dev/null +++ b/scip-kotlin-analysis/src/main/kotlin/org/scip_code/scip_java/kotlin_analysis/SymbolsCache.kt @@ -0,0 +1,575 @@ +package org.scip_code.scip_java.kotlin_analysis + +import com.intellij.psi.PsiElement +import java.lang.System.err +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaPackageSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolOrigin +import org.jetbrains.kotlin.analysis.api.symbols.KaTypeAliasSymbol +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtAnonymousInitializer +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtParameterList +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPropertyAccessor +import org.jetbrains.kotlin.psi.KtSecondaryConstructor +import org.jetbrains.kotlin.psi.KtTypeAlias +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.psi.KtVariableDeclaration +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly +import org.scip_code.scip_java.kotlin_analysis.ScipSymbolDescriptor.Kind +import org.scip_code.scip_java.shared.LocalSymbolsCache as SharedLocalSymbolsCache + +/** + * Computes SCIP symbol strings for Kotlin declarations and resolved references. + * + * Declarations with Kotlin source are classified from their PSI structure, which keeps the + * version-sensitive Analysis API surface minimal; only extension receivers are resolved. References + * that resolve to declarations without Kotlin source (classpath or JDK symbols) fall back to + * `ClassId`/`CallableId`-derived symbols. + * + * Instantiate one cache per indexed file: local symbols are numbered per SCIP document. + */ +class SymbolsCache(private val session: KaSession) { + private val globals = HashMap() + private val packages = HashMap() + private val locals = SharedLocalSymbolsCache(HashMap()) { Symbol.createLocal(it) } + + /** Synthetic members of locals need their own IDs, not global descriptor suffixes. */ + private fun memberSymbol(owner: Symbol, descriptor: ScipSymbolDescriptor): Symbol { + if (!owner.isLocal()) return Symbol.createGlobal(owner, descriptor) + val key = owner to descriptor + return locals.get(key) ?: locals.put(key) + } + + fun packageSymbol(fqName: FqName): Symbol { + if (fqName.isRoot) return Symbol.ROOT_PACKAGE + return packages.getOrPut(fqName) { + Symbol.createGlobal( + packageSymbol(fqName.parent()), + ScipSymbolDescriptor(Kind.PACKAGE, fqName.shortName().asString()), + ) + } + } + + /** The SCIP symbol for a declaration in Kotlin source. */ + fun symbolForDeclaration(declaration: KtDeclaration): Symbol { + globals[declaration]?.let { + return it + } + locals.get(declaration)?.let { + return it + } + val symbol = uncachedSymbol(declaration) + if (symbol.isGlobal()) globals[declaration] = symbol + return symbol + } + + /** + * The SCIP symbol of the implicit primary constructor of [classOrObject], which has no PSI of + * its own. Explicit constructors go through [symbolForDeclaration]. Local classes get a local + * constructor, keyed by their name or `object` keyword. + */ + fun implicitConstructorSymbol(classOrObject: KtClassOrObject): Symbol { + val owner = symbolForDeclaration(classOrObject) + if (owner.isLocal()) { + return syntheticLocalSymbol( + classOrObject.nameIdentifier + ?: (classOrObject as? KtObjectDeclaration)?.getObjectKeyword() + ?: classOrObject + ) + } + return Symbol.createGlobal(owner, ScipSymbolDescriptor(Kind.METHOD, "")) + } + + /** + * The anonymous class an enum entry body declares, which owns the body's members, as in FIR. + * Keyed by the body. + */ + fun enumEntryBodySymbol(entry: KtEnumEntry): Symbol = syntheticLocalSymbol(entry.body ?: entry) + + /** The local constructor of the anonymous class of an enum entry body. */ + fun enumEntryBodyConstructorSymbol(entry: KtEnumEntry): Symbol = + syntheticLocalSymbol(entry.nameIdentifier ?: entry) + + /** + * The synthetic getter/setter of a property (or `val`/`var` constructor parameter) without + * explicit accessor PSI. Owned by the property's container (`Class#getBanana().`), not by the + * constructor declaring the parameter. + */ + fun syntheticAccessorSymbol(property: KtDeclaration, setter: Boolean): Symbol { + val name = (property as? KtNamedDeclaration)?.name.orEmpty() + return memberSymbol(propertyOwnerSymbol(property), accessorDescriptor(name, setter)) + } + + /** The property symbol declared by a `val`/`var` constructor parameter (`Class#banana.`). */ + fun parameterPropertySymbol(parameter: KtParameter): Symbol = + memberSymbol( + propertyOwnerSymbol(parameter), + ScipSymbolDescriptor(Kind.TERM, parameter.name.orEmpty()), + ) + + /** The class symbol owning a constructor reference (`class Seagull : Bird()` → `Bird#`). */ + @OptIn(KaExperimentalApi::class) + fun constructorOwnerSymbol(session: KaSession, constructor: KaConstructorSymbol): Symbol { + val target = with(session) { constructor.originalConstructorIfTypeAliased } ?: constructor + when (val psi = target.psi) { + is KtClassOrObject -> return symbolForDeclaration(psi) + is KtConstructor<*> -> + psi.containingClassOrObject?.let { + return symbolForDeclaration(it) + } + } + return target.containingClassId?.let(::classSymbol) ?: Symbol.NONE + } + + /** The SCIP symbol of a class referenced by its (expanded) [ClassId]. */ + fun classSymbol(classId: ClassId): Symbol { + var symbol = packageSymbol(classId.packageFqName) + for (segment in classId.relativeClassName.pathSegments()) { + symbol = + Symbol.createGlobal(symbol, ScipSymbolDescriptor(Kind.TYPE, segment.asString())) + } + return symbol + } + + /** Symbol of a compiler-generated member method (enum `values()`, data-class `copy()`, …). */ + fun memberMethodSymbol(owner: KtClassOrObject, name: String): Symbol = + memberSymbol(symbolForDeclaration(owner), ScipSymbolDescriptor(Kind.METHOD, name)) + + /** Symbol of a compiler-generated member property (enum `entries`). */ + fun memberTermSymbol(owner: KtClassOrObject, name: String): Symbol = + memberSymbol(symbolForDeclaration(owner), ScipSymbolDescriptor(Kind.TERM, name)) + + /** Symbol of a parameter of a compiler-generated method. */ + fun methodParameterSymbol(method: Symbol, name: String): Symbol = + memberSymbol(method, ScipSymbolDescriptor(Kind.PARAMETER, name)) + + /** + * A per-document local symbol for a compiler-generated declaration without dedicated PSI + * (implicit `it`, ``, ``), keyed by the surrounding element. + */ + fun syntheticLocalSymbol(key: PsiElement): Symbol = locals.get(key) ?: locals.put(key) + + private fun propertyOwnerSymbol(property: KtDeclaration): Symbol = + when (property) { + is KtParameter -> + (ownerDeclarationOfParameter(property) as? KtConstructor<*>) + ?.containingClassOrObject + ?.let { symbolForDeclaration(it) } ?: Symbol.NONE + else -> ownerSymbol(property) + } + + /** The `(value)` parameter symbol of a synthetic setter. */ + fun syntheticSetterValueSymbol(property: KtDeclaration): Symbol = + memberSymbol( + syntheticAccessorSymbol(property, setter = true), + ScipSymbolDescriptor(Kind.PARAMETER, "value"), + ) + + /** + * The implicit `it` parameter of a lambda, keyed by the function literal PSI — safe because + * anonymous functions never cache a symbol of their own. + */ + fun implicitItSymbol(literal: KtFunctionLiteral): Symbol = syntheticLocalSymbol(literal) + + /** The SCIP symbol for the target of a resolved reference. */ + @OptIn(KaExperimentalApi::class) + fun symbolForReference(session: KaSession, target: KaSymbol): Symbol { + // A constructor call through a type alias (`HashMap()`) references the aliased + // class's constructor, like javac and scip-kotlinc. + if (target is KaConstructorSymbol) { + with(session) { target.originalConstructorIfTypeAliased } + ?.let { + return symbolForReference(session, it) + } + } + val psi = target.psi + if (psi is KtDeclaration) { + // A constructor symbol whose PSI is the class itself is an implicit primary. + if (target is KaConstructorSymbol && psi is KtClassOrObject) { + return implicitConstructorSymbol(psi) + } + // The implicit `it` parameter's PSI is the enclosing function literal. + if (psi is KtFunctionLiteral && target !is KaFunctionSymbol) { + return implicitItSymbol(psi) + } + // Source type aliases are expanded like library ones, matching scip-kotlinc. + if (psi is KtTypeAlias && target is KaTypeAliasSymbol) { + return with(session) { (target.expandedType as? KaClassType)?.classId } + ?.let(::classSymbol) ?: symbolForDeclaration(psi) + } + // Compiler-generated callables (data-class componentN/copy, enum + // `entries`, …) resolve to PSI of the class or parameter they derive + // from; their symbol comes from the callable id instead. + if ( + target is KaFunctionSymbol && + target !is KaConstructorSymbol && + psi !is KtNamedFunction && + psi !is KtPropertyAccessor && + psi !is KtFunctionLiteral + ) { + // Generated functions of local classes have no callable ID. Use + // the same owner/name key as their synthetic definitions. + if (target is KaNamedFunctionSymbol) { + val owner = with(session) { target.containingSymbol?.psi } as? KtClassOrObject + if (owner != null && symbolForDeclaration(owner).isLocal()) { + return memberMethodSymbol(owner, target.name.asString()) + } + } + return externalCallableSymbol(session, target) + } + if (target is KaCallableSymbol && psi is KtClassOrObject) { + return externalCallableSymbol(session, target) + } + return symbolForDeclaration(psi) + } + return when (target) { + is KaPackageSymbol -> packageSymbol(target.fqName) + is KaConstructorSymbol -> + target.containingClassId?.let { classId -> + Symbol.createGlobal( + classSymbol(classId), + ScipSymbolDescriptor( + Kind.METHOD, + "", + externalConstructorDisambiguator(session, classId, target), + ), + ) + } ?: Symbol.NONE + // Library type aliases are expanded to the aliased class (`AutoCloseable` + // → `java/lang/AutoCloseable#`), unifying Kotlin and Java references. + is KaTypeAliasSymbol -> + with(session) { (target.expandedType as? KaClassType)?.classId }?.let(::classSymbol) + ?: target.classId?.let(::classSymbol) + ?: Symbol.NONE + is KaClassLikeSymbol -> target.classId?.let(::classSymbol) ?: Symbol.NONE + is KaCallableSymbol -> externalCallableSymbol(session, target) + else -> Symbol.NONE + } + } + + /** The getter/setter symbol of an external (classpath) property reference. */ + fun externalAccessorSymbol( + session: KaSession, + resolved: KaCallableSymbol, + setter: Boolean, + ): Symbol { + val target = with(session) { resolved.fakeOverrideOriginal } + val callableId = target.callableId ?: return Symbol.NONE + val owner = + callableId.classId?.let(::classSymbol) + ?: receiverClassId(target)?.let { extensionOwnerSymbol(callableId.packageName, it) } + ?: packageSymbol(callableId.packageName) + return Symbol.createGlobal( + owner, + accessorDescriptor(callableId.callableName.asString(), setter), + ) + } + + private fun accessorDescriptor(propertyName: String, setter: Boolean): ScipSymbolDescriptor = + ScipSymbolDescriptor( + Kind.METHOD, + (if (setter) "set" else "get") + propertyName.capitalizeAsciiOnly(), + ) + + private fun uncachedSymbol(declaration: KtDeclaration): Symbol { + // Anonymous functions and lambdas have no symbol of their own, mirroring FIR. + if (declaration is KtFunctionLiteral) return Symbol.NONE + if (declaration is KtNamedFunction && declaration.name == null) return Symbol.NONE + + // Anonymous objects are local, like their members. + if (declaration is KtObjectDeclaration && declaration.isObjectLiteral()) { + return locals.put(declaration) + } + + if (isLocalMember(declaration)) return locals.put(declaration) + + val owner = ownerSymbol(declaration) + if (owner.isLocal() || owner == Symbol.NONE) return locals.put(declaration) + + return Symbol.createGlobal(owner, scipDescriptor(declaration)) + } + + /** + * Port of FIR's `isLocalMember`: functions, variables and classes declared in code bodies are + * local. Members of any class-like container — including anonymous objects — are not: locality + * is decided by the nearest scope owner, not by syntactic nesting. + */ + private fun isLocalMember(declaration: KtDeclaration): Boolean { + when (declaration) { + is KtNamedFunction, + is KtVariableDeclaration, + is KtClassOrObject -> {} + else -> return false + } + for (parent in declaration.parents) { + when (parent) { + is KtClassBody, + is KtFile -> return false + is KtBlockExpression, + is KtFunction, + is KtPropertyAccessor, + is KtProperty, + is KtAnonymousInitializer -> return true + } + } + return false + } + + private fun ownerSymbol(declaration: KtDeclaration): Symbol = + when (declaration) { + is KtTypeParameter -> + declaration.getStrictParentOfType()?.let { + symbolForDeclaration(it) + } ?: Symbol.NONE + is KtParameter -> + ownerDeclarationOfParameter(declaration)?.let { symbolForDeclaration(it) } + ?: Symbol.NONE + is KtPropertyAccessor -> ownerSymbol(declaration.property) + else -> + declaration.containingClassOrObject?.let { + if (it is KtEnumEntry) enumEntryBodySymbol(it) else symbolForDeclaration(it) + } + ?: sourceExtensionReceiver(declaration)?.let { + extensionOwnerSymbol(declaration.containingKtFile.packageFqName, it) + } + ?: packageSymbol(declaration.containingKtFile.packageFqName) + } + + /** + * Top-level extensions are owned by their receiver type, nested in the extension's own package + * (`sample/String#foo().` for `fun String.foo()` in `sample`), like scip-kotlinc. + */ + private fun extensionOwnerSymbol(packageFqName: FqName, receiver: ClassId): Symbol = + Symbol.createGlobal( + packageSymbol(packageFqName), + ScipSymbolDescriptor(Kind.TYPE, receiver.shortClassName.asString()), + ) + + private fun sourceExtensionReceiver(declaration: KtDeclaration): ClassId? { + if (declaration !is KtCallableDeclaration || declaration.receiverTypeReference == null) { + return null + } + return with(session) { declaration.symbol as? KaCallableSymbol }?.let(::receiverClassId) + } + + private fun receiverClassId(callable: KaCallableSymbol): ClassId? = + with(session) { (callable.receiverParameter?.returnType as? KaClassType)?.classId } + + /** + * The disambiguator of a top-level extension function: like scip-kotlinc, it counts the + * same-named members of the receiver class (when it is in the extension's package) followed by + * the same-named extensions on that receiver in the package. + */ + private fun extensionDisambiguator( + function: KaCallableSymbol, + packageFqName: FqName, + name: Name, + receiver: ClassId, + ): String { + val extensions = + with(session) { + findTopLevelCallables(packageFqName, name) + .filter { it is KaNamedFunctionSymbol && receiverClassId(it) == receiver } + .toList() + } + val index = extensions.indexOf(function) + if (index < 0) return "()" + val members = + if (receiver.packageFqName != packageFqName) 0 + else + with(session) { + findClass(receiver)?.declaredMemberScope?.callables(name)?.count() ?: 0 + } + val count = members + index + return if (count == 0) "()" else "(+$count)" + } + + private fun scipDescriptor(declaration: KtDeclaration): ScipSymbolDescriptor = + when { + declaration is KtEnumEntry -> + ScipSymbolDescriptor(Kind.TERM, declaration.name.orEmpty()) + declaration is KtObjectDeclaration && declaration.isCompanion() -> + ScipSymbolDescriptor(Kind.TYPE, declaration.name ?: "Companion") + declaration is KtClassOrObject -> + ScipSymbolDescriptor(Kind.TYPE, declaration.name.orEmpty()) + declaration is KtPropertyAccessor && declaration.isSetter -> + accessorDescriptor(declaration.property.name.orEmpty(), setter = true) + declaration is KtPropertyAccessor -> + accessorDescriptor(declaration.property.name.orEmpty(), setter = false) + declaration is KtConstructor<*> -> + ScipSymbolDescriptor(Kind.METHOD, "", constructorDisambiguator(declaration)) + declaration is KtNamedFunction -> + ScipSymbolDescriptor( + Kind.METHOD, + declaration.name.orEmpty(), + methodDisambiguator(declaration), + ) + declaration is KtTypeAlias -> + ScipSymbolDescriptor(Kind.TYPE, declaration.name.orEmpty()) + declaration is KtTypeParameter -> + ScipSymbolDescriptor(Kind.TYPE_PARAMETER, declaration.name.orEmpty()) + declaration is KtParameter -> + ScipSymbolDescriptor(Kind.PARAMETER, declaration.name.orEmpty()) + declaration is KtVariableDeclaration -> + ScipSymbolDescriptor(Kind.TERM, declaration.name.orEmpty()) + else -> { + err.println("unknown declaration kind ${declaration.javaClass.simpleName}") + ScipSymbolDescriptor.NONE + } + } + + /** + * Port of FIR's `methodDisambiguator`: the N-th preceding function with the same name in the + * containing scope yields `(+N)`, the first one yields `()`. Classes and properties of the same + * name don't count, as in [externalMethodDisambiguator] and the javac indexer. + */ + private fun methodDisambiguator(function: KtNamedFunction): String { + val name = function.name ?: return "()" + if (function.containingClassOrObject == null) { + sourceExtensionReceiver(function)?.let { receiver -> + val symbol = with(session) { function.symbol } + return extensionDisambiguator( + symbol, + function.containingKtFile.packageFqName, + Name.identifier(name), + receiver, + ) + } + } + var count = 0 + var found = false + for (sibling in siblingDeclarations(function)) { + if (sibling == function) { + found = true + break + } + if (sibling is KtNamedFunction && sibling.name == name) count++ + } + if (count == 0 || !found) return "()" + return "(+$count)" + } + + private fun constructorDisambiguator(constructor: KtConstructor<*>): String { + val containingClass = constructor.containingClassOrObject ?: return "()" + val constructors = + listOfNotNull(containingClass.primaryConstructor) + + containingClass.declarations.filterIsInstance() + val index = constructors.indexOf(constructor) + return if (index <= 0) "()" else "(+$index)" + } + + private fun siblingDeclarations(declaration: KtDeclaration): List = + when (val containingClass = declaration.containingClassOrObject) { + null -> declaration.containingKtFile.declarations + else -> listOfNotNull(containingClass.primaryConstructor) + containingClass.declarations + } + + private fun ownerDeclarationOfParameter(parameter: KtParameter): KtDeclaration? = + (parameter.parent as? KtParameterList)?.parent as? KtDeclaration + + internal fun isDeclarationParameter(parameter: KtParameter): Boolean = + ownerDeclarationOfParameter(parameter) != null + + /** + * External (classpath/JDK) callables, disambiguated by their position among the same-named + * callables of their container — mirroring FIR, which consulted the class's declared members or + * the top-level symbol provider (e.g. `kotlin/collections/forEachIndexed(+9).`). + */ + private fun externalCallableSymbol(session: KaSession, resolved: KaCallableSymbol): Symbol { + // Inherited members must use their declaration's owner and overload number. + val target = with(session) { resolved.fakeOverrideOriginal } + val callableId = target.callableId ?: return Symbol.NONE + val owner = + callableId.classId?.let(::classSymbol) + ?: receiverClassId(target)?.let { extensionOwnerSymbol(callableId.packageName, it) } + ?: packageSymbol(callableId.packageName) + val name = callableId.callableName.asString() + val descriptor = + when (target) { + is KaFunctionSymbol -> + ScipSymbolDescriptor( + Kind.METHOD, + name, + externalMethodDisambiguator(session, target), + ) + else -> ScipSymbolDescriptor(Kind.TERM, name) + } + return Symbol.createGlobal(owner, descriptor) + } + + /** Like javac: constructors in declaration order, private ones included. */ + private fun externalConstructorDisambiguator( + session: KaSession, + classId: ClassId, + target: KaConstructorSymbol, + ): String { + val constructors = + with(session) { findClass(classId)?.declaredMemberScope?.constructors?.toList() } + ?: return "()" + val index = constructors.indexOf(target) + return if (index <= 0) "()" else "(+$index)" + } + + private fun externalMethodDisambiguator(session: KaSession, target: KaCallableSymbol): String { + val callableId = target.callableId ?: return "()" + val overloads: List = + with(session) { + val classId = callableId.classId + if (classId == null) { + receiverClassId(target)?.let { receiver -> + return extensionDisambiguator( + target, + callableId.packageName, + callableId.callableName, + receiver, + ) + } + findTopLevelCallables(callableId.packageName, callableId.callableName) + .filterIsInstance() + .toList() + } else { + val classSymbol = findClass(classId) ?: return "()" + classSymbol.declaredMemberScope + .callables(callableId.callableName) + .filterIsInstance() + .toList() + } + } + val index = overloads.indexOfFirst { it == target } + // Source declaredMemberScope omits generated Any overrides. They follow + // the explicit overloads in the compiled class, rather than sharing index 0. + if (index < 0 && target.origin == KaSymbolOrigin.SOURCE_MEMBER_GENERATED) { + return if (overloads.isEmpty()) "()" else "(+${overloads.size})" + } + return if (index <= 0) "()" else "(+$index)" + } +} diff --git a/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt b/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt new file mode 100644 index 000000000..c44545eaf --- /dev/null +++ b/scip-kotlin-analysis/src/test/kotlin/org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest.kt @@ -0,0 +1,444 @@ +package org.scip_code.scip_java.kotlin_analysis + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertTrue +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.SymbolRole + +class KotlinAnalysisIndexerTest { + + @Test + fun `indexes definitions, references and locals across files`() { + val sourceroot = Files.createTempDirectory("scip-kotlin-analysis").toRealPath() + val src = Files.createDirectories(sourceroot.resolve("src")) + Files.writeString( + src.resolve("Greeter.kt"), + """ + |package snapshots + | + |class Greeter(val name: String) { + | fun greet(): String { + | val message = "Hello, " + name + | return message + | } + | + | fun greet(punctuation: String): String = greet() + punctuation + |} + """ + .trimMargin(), + ) + Files.writeString( + src.resolve("Main.kt"), + """ + |package snapshots + | + |fun useGreeter(): String { + | val greeter = Greeter("world") + | return greeter.greet("!") + |} + """ + .trimMargin(), + ) + val targetroot = sourceroot.resolve("scip-targetroot") + + val documents = + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot, + sourceRoots = listOf(src), + classpath = listOf(kotlinStdlibJar()), + ) + .run() + .sortedBy { it.relativePath } + + assertEquals(listOf("src/Greeter.kt", "src/Main.kt"), documents.map { it.relativePath }) + val greeter = documents[0] + val main = documents[1] + + val greeterDefinitions = definitions(greeter) + assertContains(greeterDefinitions, "snapshots/Greeter#") + assertContains(greeterDefinitions, "snapshots/Greeter#``().(name)") + assertContains(greeterDefinitions, "snapshots/Greeter#greet().") + assertContains(greeterDefinitions, "snapshots/Greeter#greet(+1).") + assertContains(greeterDefinitions, "snapshots/Greeter#greet(+1).(punctuation)") + // `val message` inside the function body is the first local of the file. + assertContains(greeterDefinitions, "local 0") + // The overload delegating to `greet()` references it. + assertContains(references(greeter), "snapshots/Greeter#greet().") + // The stdlib type reference resolves to a classpath symbol. + assertContains(references(greeter), "kotlin/String#") + // A `val` constructor parameter's property belongs to the class, and its + // synthetic getter to the property. + val enclosingSymbols = greeter.symbolsList.associate { it.symbol to it.enclosingSymbol } + assertEquals( + "snapshots/Greeter#``().", + enclosingSymbols["snapshots/Greeter#``().(name)"], + ) + assertEquals("snapshots/Greeter#", enclosingSymbols["snapshots/Greeter#name."]) + assertEquals("snapshots/Greeter#name.", enclosingSymbols["snapshots/Greeter#getName()."]) + + val mainDefinitions = definitions(main) + assertContains(mainDefinitions, "snapshots/useGreeter().") + assertContains(mainDefinitions, "local 0") + val mainReferences = references(main) + // Cross-file references: constructor call, method call on the overload, + // and the local variable usage. + assertContains(mainReferences, "snapshots/Greeter#``().") + assertContains(mainReferences, "snapshots/Greeter#greet(+1).") + assertContains(mainReferences, "local 0") + + assertTrue( + Files.exists(targetroot.resolve("META-INF/scip/src/Greeter.kt.scip")), + "expected SCIP shard for Greeter.kt", + ) + assertTrue( + Files.exists(targetroot.resolve("META-INF/scip/src/Main.kt.scip")), + "expected SCIP shard for Main.kt", + ) + } + + @Test + fun `an analysis failure skips the entire file and continues indexing`(@TempDir path: Path) { + val sourceroot = path.toRealPath() + val src = Files.createDirectories(sourceroot.resolve("src")) + val broken = src.resolve("Broken.kt") + Files.writeString( + broken, + """ + package snapshots + + fun before(): Int = 1 + val (first, second) = Pair(1, 2) + fun after(): Int = before() + """ + .trimIndent(), + ) + val other = src.resolve("Other.kt") + Files.writeString( + other, + """ + package snapshots + + fun other(): Int = after() + """ + .trimIndent(), + ) + val targetroot = sourceroot.resolve("scip-targetroot") + val emitted = mutableListOf() + val documents = + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = targetroot, + sourceRoots = listOf(broken, other), + classpath = listOf(kotlinStdlibJar()), + callback = { emitted.add(it) }, + ) + .run() + + assertEquals(listOf("src/Other.kt"), documents.map { it.relativePath }) + assertEquals(documents, emitted) + assertContains(definitions(documents.single()), "snapshots/other().") + assertContains(references(documents.single()), "snapshots/after().") + assertTrue(Files.notExists(targetroot.resolve("META-INF/scip/src/Broken.kt.scip"))) + assertTrue(Files.exists(targetroot.resolve("META-INF/scip/src/Other.kt.scip"))) + } + + // Compiled by the test build, so references use classpath symbols rather than source PSI. + open class Base(open var state: T) { + open fun find(value: T): T = value + + fun find(index: Int): T = state + } + + open class Middle : Base("") + + class Leaf : Middle() + + class Overriding : Middle() { + override var state: String = "override" + + override fun find(value: String): String = value + } + + @Test + fun `inherited binary members reference their declarations`(@TempDir path: Path) { + val sourceroot = path.toRealPath() + val library = Paths.get(Leaf::class.java.protectionDomain.codeSource.location.toURI()) + val code = + """ + package sample + + import org.scip_code.scip_java.kotlin_analysis.KotlinAnalysisIndexerTest.Leaf + import org.scip_code.scip_java.kotlin_analysis.KotlinAnalysisIndexerTest.Overriding + + fun use(leaf: Leaf, overriding: Overriding) { + leaf.find("value") + leaf.find(1) + leaf.state = "updated" + overriding.find("value") + overriding.find(1) + overriding.state = "updated" + } + """ + .trimIndent() + val source = sourceroot.resolve("Use.kt") + Files.writeString(source, code) + val document = + KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = sourceroot.resolve("scip-targetroot"), + sourceRoots = listOf(source), + classpath = listOf(kotlinStdlibJar(), library), + ) + .run() + .single() + val prefix = "org/scip_code/scip_java/kotlin_analysis/KotlinAnalysisIndexerTest#" + val expectedReferences = + mapOf( + "leaf.find(\"value\")" to setOf("${prefix}Base#find()."), + "leaf.find(1)" to setOf("${prefix}Base#find(+1)."), + "leaf.state" to + setOf( + "${prefix}Base#state.", + "${prefix}Base#getState().", + "${prefix}Base#setState().", + ), + "overriding.find(\"value\")" to setOf("${prefix}Overriding#find()."), + "overriding.find(1)" to setOf("${prefix}Base#find(+1)."), + "overriding.state" to + setOf( + "${prefix}Overriding#state.", + "${prefix}Overriding#getState().", + "${prefix}Overriding#setState().", + ), + ) + for ((expression, expected) in expectedReferences) { + val line = code.lines().indexOfFirst { expression in it } + val start = code.lines()[line].indexOf(expression) + expression.indexOf('.') + 1 + val actual = + document.occurrencesList + .filter { + it.singleLineRange.line == line && + it.singleLineRange.startCharacter == start + } + .map { it.symbol } + .toSet() + assertEquals(expected, actual, expression) + } + } + + @Test + fun `classes and properties are not overloads of same-named functions`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class Offset(val seconds: Int) { + val shift: Int = 0 + fun shift(by: Int): Int = by + } + + fun Offset(): Offset = Offset(0) + """, + ) + + // References to compiled classes number only functions, as does javac. + assertContainsAll(definitions(document), "sample/Offset().", "sample/Offset#shift().") + } + + @Test + fun `data class object methods are defined only when generated`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + data class Generated(val value: Int) { + fun equals(value: Int): Boolean = this.value == value + fun hashCode(seed: Int): Int = seed + fun toString(prefix: String): String = prefix + } + data class Explicit(val value: Int) { + override fun equals(other: Any?): Boolean = false + override fun hashCode(): Int = value + override fun toString(): String = "explicit" + } + open class FinalBase { + final override fun equals(other: Any?): Boolean = false + final override fun hashCode(): Int = 1 + final override fun toString(): String = "inherited" + } + data class Inherited(val value: Int) : FinalBase() + fun use(generated: Generated, explicit: Explicit, inherited: Inherited) { + generated.equals(null) + generated.hashCode() + generated.toString() + explicit.equals(null) + explicit.hashCode() + explicit.toString() + inherited.equals(null) + inherited.hashCode() + inherited.toString() + } + """, + ) + val defined = definitions(document) + for (name in listOf("equals", "hashCode", "toString")) { + // Same-named overloads must not suppress or collide with the generated method. + for (symbol in + listOf( + "sample/Generated#$name(+1).", + "sample/Explicit#$name().", + "sample/FinalBase#$name().", + )) { + assertEquals(1, defined.count { it == symbol }, symbol) + assertContains(references(document), symbol) + } + assertContains(defined, "sample/Generated#$name().") + assertTrue(defined.none { it.startsWith("sample/Inherited#$name(") }) + } + assertContains(defined, "sample/Generated#equals(+1).(other)") + } + + @Test + fun `synthetic members of local classes have distinct local symbols`(@TempDir path: Path) { + val code = + """ + package sample + + fun use() { + data class First(var version: Int, val label: String) + data class Second(val version: Int) + val first = First(1, "one") + val second = Second(2) + first.version = 3 + println(second.version) + first.copy() + second.copy() + first.component1() + first.component2() + second.component1() + first.equals(null) + first.hashCode() + first.toString() + second.equals(null) + second.hashCode() + second.toString() + val list = object : java.util.ArrayList() {} + list.clear() + } + """ + .trimIndent() + val document = compileScip(path, code) + val locals = document.symbolsList.filter { it.symbol.startsWith("local ") } + val allSymbols = + document.occurrencesList.map { it.symbol } + + document.symbolsList.flatMap { + listOf(it.symbol, it.enclosingSymbol) + it.relationshipsList.map { it.symbol } + } + assertTrue( + allSymbols.filter { it.startsWith("local ") }.all { it.matches(Regex("local [0-9]+")) }, + "malformed locals: ${allSymbols.filter { it.startsWith("local ") }.distinct()}", + ) + assertEquals(locals.size, locals.map { it.symbol }.distinct().size) + val localDefinitions = definitions(document).filter { it.startsWith("local ") }.toSet() + assertTrue( + references(document).filter { it.startsWith("local ") }.all { it in localDefinitions } + ) + + for ((className, receiver) in listOf("First" to "first", "Second" to "second")) { + val owner = locals.single { it.kind == Kind.Class && it.displayName == className } + val members = locals.filter { it.enclosingSymbol == owner.symbol } + val property = + members.single { it.kind == Kind.Property && it.displayName == "version" } + val accessors = locals.filter { it.enclosingSymbol == property.symbol } + assertEquals(if (className == "First") 2 else 1, accessors.size) + if (className == "First") { + val setter = accessors.single { it.signatureDocumentation.text.contains("set(") } + assertEquals( + 1, + locals.count { + it.enclosingSymbol == setter.symbol && it.kind == Kind.Parameter + }, + ) + } + val copy = members.single { it.kind == Kind.Method && it.displayName == "copy" } + assertEquals( + if (className == "First") setOf("version", "label") else setOf("version"), + locals.filter { it.enclosingSymbol == copy.symbol }.map { it.displayName }.toSet(), + ) + val expectedReferences = + mapOf( + "$receiver.version" to (setOf(property.symbol) + accessors.map { it.symbol }), + "$receiver.copy()" to setOf(copy.symbol), + "$receiver.equals(null)" to + setOf(members.single { it.displayName == "equals" }.symbol), + ) + + members + .filter { + it.displayName.startsWith("component") || + it.displayName in setOf("hashCode", "toString") + } + .associate { "$receiver.${it.displayName}()" to setOf(it.symbol) } + assertEquals(if (className == "First") 7 else 6, expectedReferences.size) + for ((expression, expected) in expectedReferences) { + val line = code.lines().indexOfFirst { expression in it } + val start = code.lines()[line].indexOf(expression) + receiver.length + 1 + val actual = + document.occurrencesList + .filter { + it.singleLineRange.line == line && + it.singleLineRange.startCharacter == start + } + .map { it.symbol } + .toSet() + assertEquals(expected, actual, expression) + } + } + } + + private fun compileScip(path: Path, code: String): Document { + val sourceroot = path.toRealPath() + val source = Files.createDirectories(sourceroot.resolve("src")).resolve("Test.kt") + Files.writeString(source, code.trimIndent()) + return KotlinAnalysisIndexer( + sourceroot = sourceroot, + targetroot = sourceroot.resolve("scip-targetroot"), + sourceRoots = listOf(source), + classpath = listOf(kotlinStdlibJar()), + ) + .run() + .single() + } + + private fun assertContainsAll(actual: List, vararg expected: T) { + val missing = expected.filterNot { it in actual } + assertTrue( + missing.isEmpty(), + "missing:\n${missing.joinToString("\n")}\nactual:\n${actual.joinToString("\n")}", + ) + } + + private fun definitions(document: Document): List = + document.occurrencesList + .filter { it.symbolRoles and SymbolRole.Definition.number != 0 } + .map { it.symbol } + + private fun references(document: Document): List = + document.occurrencesList + .filter { it.symbolRoles and SymbolRole.Definition.number == 0 } + .map { it.symbol } + + private fun kotlinStdlibJar(): Path = + Paths.get(Unit::class.java.protectionDomain.codeSource.location.toURI()) +} diff --git a/scip-kotlinc/build.gradle.kts b/scip-kotlinc/build.gradle.kts deleted file mode 100644 index 0a10173c5..000000000 --- a/scip-kotlinc/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("scip.java-library") - id("scip.kotlin-jvm") - id("scip.shadow-producer") - id("scip.maven-publish") -} - -description = "A kotlinc plugin to emit SCIP information" - -dependencies { - implementation(project(":scip-shared")) - implementation(libs.scip.kotlin.bindings) - compileOnly(libs.kotlin.stdlib) - compileOnly(libs.kotlin.compiler.embeddable) - - testImplementation(libs.kotlin.compiler.embeddable) - testImplementation(libs.kotlin.test) - testImplementation(libs.kotlin.test.junit5) - testImplementation(libs.kotlin.reflect) - testImplementation(libs.kotest.assertions.core) - testImplementation(libs.kctfork.core) -} - -tasks.named("test") { - maxHeapSize = "2g" - dependsOn("testKotlin240") -} - -// The plugin runs inside the indexed project's compiler, which can predate our build compiler. -val kotlin240TestRuntime = configurations.create("kotlin240TestRuntime") { - extendsFrom(configurations.testRuntimeClasspath.get()) - resolutionStrategy.force("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.21") -} - -tasks.register("testKotlin240") { - useJUnitPlatform() - testClassesDirs = sourceSets.test.get().output.classesDirs - classpath = sourceSets.test.get().output + sourceSets.main.get().output + kotlin240TestRuntime - maxHeapSize = "2g" -} - -tasks.named("shadowJar") { - mergeServiceFiles() -} 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 deleted file mode 100644 index 366752b21..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ /dev/null @@ -1,467 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.nio.file.Path -import org.jetbrains.kotlin.* -import org.jetbrains.kotlin.com.intellij.lang.LighterASTNode -import org.jetbrains.kotlin.com.intellij.util.diff.FlyweightCapableTreeStructure -import org.jetbrains.kotlin.diagnostics.* -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind -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.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.FirClassLikeSymbol -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 - -open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtension(session) { - companion object { - val visitors: MutableMap = mutableMapOf() - - private fun getIdentifier(element: KtSourceElement): KtSourceElement = - 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 - get() = AnalyzerDeclarationCheckers(session.analyzerParamsProvider.sourceroot) - - override val expressionCheckers: ExpressionCheckers - get() = - object : ExpressionCheckers() { - override val qualifiedAccessExpressionCheckers: - Set = - setOf(SemanticQualifiedAccessExpressionChecker()) - - override val resolvedQualifierCheckers: Set = - setOf(SemanticResolvedQualifierChecker()) - - override val typeOperatorCallCheckers: Set = - setOf(SemanticClassReferenceExpressionChecker()) - } - - open class AnalyzerDeclarationCheckers(sourceroot: Path) : DeclarationCheckers() { - override val fileCheckers: Set = - setOf(SemanticFileChecker(sourceroot), SemanticImportsChecker()) - override val classLikeCheckers: Set = setOf(SemanticClassLikeChecker()) - override val constructorCheckers: Set = - setOf(SemanticConstructorChecker()) - // The specialized checker property was renamed in Kotlin 2.4.20. - override val functionCheckers: Set = - setOf(SemanticSimpleFunctionChecker()) - override val anonymousFunctionCheckers: Set = - setOf(SemanticAnonymousFunctionChecker()) - override val propertyCheckers: Set = setOf(SemanticPropertyChecker()) - override val valueParameterCheckers: Set = - setOf(SemanticValueParameterChecker()) - override val typeParameterCheckers: Set = - setOf(SemanticTypeParameterChecker()) - 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) : - FirFileChecker(MppCheckerKind.Common) { - companion object { - val globals = GlobalSymbolsCache() - } - - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirFile) { - val ktFile = declaration.sourceFile ?: return - val lineMap = LineMap(declaration) - val visitor = ScipVisitor(sourceroot, ktFile, lineMap, globals) - visitors[ktFile] = visitor - } - } - - class SemanticImportsChecker : FirFileChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirFile) { - val ktFile = declaration.sourceFile ?: return - val visitor = visitors[ktFile] - - val eachFqNameElement = - { - fqName: FqName, - tree: FlyweightCapableTreeStructure, - names: LighterASTNode, - callback: (FqName, KtLightSourceElement) -> Unit -> - val nameList = - if (names.tokenType == KtNodeTypes.REFERENCE_EXPRESSION) listOf(names) - else tree.collectDescendantsOfType(names, KtNodeTypes.REFERENCE_EXPRESSION) - - var ancestor = fqName - var depth = 0 - - while (ancestor != FqName.ROOT) { - val nameNode = nameList[nameList.lastIndex - depth] - val nameSource = nameNode.toKtLightSourceElement(tree) - - callback(ancestor, nameSource) - - ancestor = ancestor.parent() - depth++ - } - } - - val packageDirective = declaration.packageDirective - val fqName = packageDirective.packageFqName - val source = packageDirective.source - if (source != null) { - val names = - source.treeStructure.findChildByType( - source.lighterASTNode, - KtNodeTypes.DOT_QUALIFIED_EXPRESSION, - ) - ?: source.treeStructure.findChildByType( - source.lighterASTNode, - KtNodeTypes.REFERENCE_EXPRESSION, - ) - - if (names != null) { - eachFqNameElement(fqName, source.treeStructure, names) { fqName, name -> - visitor?.visitPackage(fqName, name) - } - } - } - - declaration.imports.forEach { import -> - val source = import.source ?: return@forEach - val fqName = import.importedFqName ?: return@forEach - - val names = - source.treeStructure.findDescendantByType( - source.lighterASTNode, - KtNodeTypes.DOT_QUALIFIED_EXPRESSION, - ) - if (names != null) { - eachFqNameElement(fqName, source.treeStructure, names) { fqName, name -> - val symbolProvider = context.session.symbolProvider - - val klass = - symbolProvider.getClassLikeSymbolByClassId(ClassId.topLevel(fqName)) - val callables = - symbolProvider.getTopLevelCallableSymbols( - fqName.parent(), - fqName.shortName(), - ) - - if (klass != null) { - visitor?.visitClassReference(klass, name) - } else if (callables.isNotEmpty()) { - for (callable in callables) { - visitor?.visitCallableReference(callable, name) - } - } else { - visitor?.visitPackage(fqName, name) - } - } - } - } - } - } - - private class SemanticClassLikeChecker : FirClassLikeChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirClassLikeDeclaration) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - val objectKeyword = - if (declaration is FirAnonymousObject) { - source.treeStructure - .findChildByType(source.lighterASTNode, KtTokens.OBJECT_KEYWORD) - ?.toKtLightSourceElement(source.treeStructure) - } 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 ?: companionKeyword ?: identifierSource, - enclosingSource = source, - ) - - if (declaration is FirClass) { - for (superType in declaration.superTypeRefs) { - visitor?.emitTypeRef(superType) - } - } - } - } - - private class SemanticConstructorChecker : FirConstructorChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirConstructor) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - - if (declaration.isPrimary) { - // if the constructor is not denoted by the 'constructor' keyword, we want to link - // it to the - // class identifier - val klass = declaration.symbol.getContainingClassSymbol() - val klassSource = klass?.source ?: source - val constructorKeyboard = - source.treeStructure - .findChildByType(source.lighterASTNode, KtTokens.CONSTRUCTOR_KEYWORD) - ?.toKtLightSourceElement(source.treeStructure) - - val objectKeyword = - if (klass is FirAnonymousObjectSymbol) { - source.treeStructure - .findChildByType(source.lighterASTNode, KtTokens.OBJECT_KEYWORD) - ?.toKtLightSourceElement(source.treeStructure) - } else { - null - } - - visitor?.visitPrimaryConstructor( - declaration, - constructorKeyboard ?: objectKeyword ?: getIdentifier(klassSource), - enclosingSource = source, - ) - } else { - visitor?.visitSecondaryConstructor( - declaration, - getIdentifier(source), - enclosingSource = source, - ) - } - } - } - - private class SemanticSimpleFunctionChecker : FirFunctionChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirFunction) { - if (declaration !is FirNamedFunction) return - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitNamedFunction( - declaration, - getIdentifier(source), - enclosingSource = source, - ) - visitor?.emitTypeRef(declaration.returnTypeRef) - declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } - } - } - - private class SemanticAnonymousFunctionChecker : - FirAnonymousFunctionChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirAnonymousFunction) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitNamedFunction(declaration, source, enclosingSource = source) - } - } - - private class SemanticPropertyChecker : FirPropertyChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirProperty) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitProperty(declaration, getIdentifier(source), enclosingSource = source) - visitor?.emitTypeRef(declaration.returnTypeRef) - declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } - } - } - - private class SemanticValueParameterChecker : FirValueParameterChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirValueParameter) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitParameter(declaration, getIdentifier(source), enclosingSource = source) - visitor?.emitTypeRef(declaration.returnTypeRef) - } - } - - private class SemanticTypeParameterChecker : FirTypeParameterChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirTypeParameter) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitTypeParameter( - declaration, - getIdentifier(source), - enclosingSource = source, - ) - } - } - - private class SemanticTypeAliasChecker : FirTypeAliasChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirTypeAlias) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - visitor?.visitTypeAlias(declaration, getIdentifier(source), enclosingSource = source) - } - } - - private class SemanticPropertyAccessorChecker : - FirPropertyAccessorChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirPropertyAccessor) { - val source = declaration.source ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - val identifierSource = - if (declaration.isGetter) { - source.treeStructure - .findChildByType(source.lighterASTNode, KtTokens.GET_KEYWORD) - ?.toKtLightSourceElement(source.treeStructure) ?: getIdentifier(source) - } else if (declaration.isSetter) { - source.treeStructure - .findChildByType(source.lighterASTNode, KtTokens.SET_KEYWORD) - ?.toKtLightSourceElement(source.treeStructure) ?: getIdentifier(source) - } else { - getIdentifier(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) { - companion object { - // Resolve once against the host compiler: symbol became qualifierSymbol in 2.4.20. - private val symbolGetter = - try { - FirResolvedQualifier::class.java.getMethod("getQualifierSymbol") - } catch (_: NoSuchMethodException) { - FirResolvedQualifier::class.java.getMethod("getSymbol") - } - } - - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(expression: FirResolvedQualifier) { - val symbol = symbolGetter.invoke(expression) as FirClassLikeSymbol<*>? ?: 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)) - } - } - - private class SemanticQualifiedAccessExpressionChecker : - FirQualifiedAccessExpressionChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(expression: FirQualifiedAccessExpression) { - val source = expression.source ?: return - val calleeReference = expression.calleeReference - if ((calleeReference as? FirResolvedNamedReference) == null) { - return - } - - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - 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() - if (referencedKlass != null) { - visitor?.visitClassReference(referencedKlass, identifierSource) - } - } - - // When encountering a reference to a property symbol, emit both getter and setter - // symbols - if (resolvedSymbol is FirPropertySymbol) { - resolvedSymbol.getterSymbol?.let { - visitor?.visitCallableReference(it, identifierSource) - } - resolvedSymbol.setterSymbol?.let { - visitor?.visitCallableReference(it, identifierSource) - } - } - } - } - - private class SemanticClassReferenceExpressionChecker : - FirTypeOperatorCallChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(expression: FirTypeOperatorCall) { - val typeRef = expression.conversionTypeRef - val source = typeRef.source ?: return - val classSymbol = typeRef.toClassLikeSymbol(context.session) ?: return - val ktFile = context.containingFileSymbol?.sourceFile ?: return - val visitor = visitors[ktFile] - - 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 deleted file mode 100644 index 601511099..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.nio.file.Path -import java.nio.file.Paths -import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption -import org.jetbrains.kotlin.compiler.plugin.CliOption -import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor -import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi -import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.config.CompilerConfigurationKey - -const val VAL_SOURCES = "sourceroot" -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 = PLUGIN_ID - override val pluginOptions: Collection = - listOf( - CliOption( - VAL_SOURCES, - "", - "the absolute path to the root of the Kotlin sources", - required = true, - ), - CliOption( - VAL_TARGET, - "", - "the absolute path to the directory where to generate SCIP files.", - required = true, - ), - ) - - override fun processOption( - option: AbstractCliOption, - value: String, - configuration: CompilerConfiguration, - ) { - when (option.optionName) { - VAL_SOURCES -> configuration.put(KEY_SOURCES, Paths.get(value)) - VAL_TARGET -> configuration.put(KEY_TARGET, Paths.get(value)) - } - } -} diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerFirExtensionRegistrar.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerFirExtensionRegistrar.kt deleted file mode 100644 index e0d6271de..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerFirExtensionRegistrar.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar -import org.scip_code.scip_java.shared.ScipOptions - -class AnalyzerFirExtensionRegistrar(private val options: ScipOptions) : FirExtensionRegistrar() { - override fun ExtensionRegistrarContext.configurePlugin() { - +AnalyzerParamsProvider.getFactory(options) - +::AnalyzerCheckers - } -} diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerParamsProvider.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerParamsProvider.kt deleted file mode 100644 index 38f64ce2a..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerParamsProvider.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.nio.file.Path -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent -import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent.Factory -import org.scip_code.scip_java.shared.ScipOptions - -open class AnalyzerParamsProvider(session: FirSession, val options: ScipOptions) : - FirExtensionSessionComponent(session) { - val sourceroot: Path - get() = options.sourceroot - - companion object { - fun getFactory(options: ScipOptions): Factory { - return Factory { AnalyzerParamsProvider(it, options) } - } - } -} - -val FirSession.analyzerParamsProvider: AnalyzerParamsProvider by - FirSession.sessionComponentAccessor() 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 deleted file mode 100644 index 5833d8883..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar -import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi -import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter -import org.scip_code.scip.Document -import org.scip_code.scip_java.shared.ScipOptions - -@OptIn(ExperimentalCompilerApi::class) -class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : CompilerPluginRegistrar() { - override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { - val options = - ScipOptions().apply { - sourceroot = configuration[KEY_SOURCES]!! - targetroot = configuration[KEY_TARGET]!! - } - FirExtensionRegistrarAdapter.registerExtension(AnalyzerFirExtensionRegistrar(options)) - IrGenerationExtension.registerExtension( - PostAnalysisExtension( - configuration = configuration, - sourceRoot = options.sourceroot, - targetRoot = options.targetroot, - callback = callback, - ) - ) - } - - override val pluginId = PLUGIN_ID - - override val supportsK2: Boolean - get() = true -} 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 deleted file mode 100644 index afd51bd0a..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import org.jetbrains.kotlin.KtSourceElement -import org.jetbrains.kotlin.com.intellij.navigation.NavigationItem -import org.jetbrains.kotlin.fir.declarations.FirFile -import org.jetbrains.kotlin.text - -/** Maps between an element and its identifier positions */ -class LineMap(private val file: FirFile) { - private fun offsetToLineAndCol(offset: Int): Pair? = - file.sourceFileLinesMapping?.getLineAndColumnByOffset(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 0-based column number for a given offset. */ - fun columnForOffset(offset: Int): Int = offsetToLineAndCol(offset)?.second ?: 0 - - /** Returns the 0-based start character. */ - fun startCharacter(element: KtSourceElement): Int = - offsetToLineAndCol(element.startOffset)?.second ?: 0 - - /** Returns the 0-based end character. */ - fun endCharacter(element: KtSourceElement): Int = - startCharacter(element) + nameForOffset(element).length - - /** Returns the 1-based line number (subtract 1 for protobuf). */ - fun lineNumber(element: KtSourceElement): Int = - file.sourceFileLinesMapping?.getLineByOffset(element.startOffset)?.let { it + 1 } ?: 0 - - companion object { - fun nameForOffset(element: KtSourceElement): String = - when (element) { - is NavigationItem -> element.name ?: element.text?.toString() ?: "" - else -> element.text?.toString() ?: "" - } - } -} 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 deleted file mode 100644 index 211ca221e..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.io.PrintWriter -import java.io.Writer -import java.nio.file.Path -import java.nio.file.Paths -import org.jetbrains.kotlin.KtSourceFile -import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageRenderer -import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector -import org.jetbrains.kotlin.config.CommonConfigurationKeys -import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.config.MessageCollectorAccess -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.scip_code.scip.Document -import org.scip_code.scip_java.shared.ScipShardPaths -import org.scip_code.scip_java.shared.ScipShardWriter - -/** - * Writes per-source SCIP shards once the FIR checkers have finished and the IR phase begins. - * - *

For each source file [AnalyzerCheckers] registered a [ScipVisitor] for, this builds the file's - * [Document] and serializes it under `/META-INF/scip/.scip`. Files - * 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, -) : IrGenerationExtension { - override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - try { - for ((ktSourceFile, visitor) in AnalyzerCheckers.visitors) { - try { - val document = visitor.build() - scipShardPathForFile(ktSourceFile)?.let { outPath -> - ScipShardWriter.writeShard(outPath, document) - } - callback(document) - } catch (e: Exception) { - handleException(e) - } - } - } catch (e: Exception) { - handleException(e) - } - AnalyzerCheckers.visitors.clear() - } - - private fun scipShardPathForFile(file: KtSourceFile): Path? { - val normalizedPath = Paths.get(file.path).normalize() - val outPath = ScipShardPaths.shardPath(targetRoot, sourceRoot, normalizedPath) - if (outPath.isPresent) { - return outPath.get() - } - System.err.println( - "given file is not under the sourceroot.\n\tSourceroot: $sourceRoot\n\tFile path: ${file.path}\n\tNormalized file path: $normalizedPath" - ) - return null - } - - @OptIn(MessageCollectorAccess::class) - private val messageCollector = - configuration.get( - CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, - PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false), - ) - - private fun handleException(e: Exception) { - val writer = - PrintWriter( - object : Writer() { - val buf = StringBuffer() - - override fun close() = - messageCollector.report(CompilerMessageSeverity.WARNING, buf.toString()) - - override fun flush() = Unit - - override fun write(data: CharArray, offset: Int, len: Int) { - buf.append(data, offset, len) - } - }, - false, - ) - writer.println("Exception in scip-kotlin compiler plugin:") - e.printStackTrace(writer) - writer.println( - "Please report a bug to https://github.com/scip-code/scip-java with the stack trace above." - ) - writer.close() - } -} 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 deleted file mode 100644 index 4e904b3ef..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ /dev/null @@ -1,225 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.nio.file.Path -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 -import org.jetbrains.kotlin.fir.symbols.impl.* -import org.jetbrains.kotlin.fir.types.impl.FirImplicitAnyTypeRef -import org.jetbrains.kotlin.lexer.KtTokens -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 -import org.scip_code.scip.symbolInformation -import org.scip_code.scip_java.shared.ScipDocumentBuilder -import org.scip_code.scip_java.shared.ScipRange -import org.scip_code.scip_java.shared.ScipShardPaths - -/** Builds a SCIP [Document] for a single Kotlin source file. */ -class ScipTextDocumentBuilder( - private val sourceroot: Path, - private val file: KtSourceFile, - private val lineMap: LineMap, - private val cache: SymbolsCache, -) { - private val documentBuilder = ScipDocumentBuilder() - private val fileText = file.getContentsAsStream().reader().readText() - - fun build(): Document = documentBuilder.build("kotlin", relativePath(), fileText) - - context(context: CheckerContext) - fun emitScipData( - firBasedSymbol: FirBasedSymbol<*>?, - symbol: Symbol, - element: KtSourceElement, - isDefinition: Boolean, - enclosingSource: KtSourceElement? = null, - ) { - documentBuilder.addOccurrence(occurrence(symbol, element, isDefinition, enclosingSource)) - if (isDefinition) { - documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element)) - } - } - - @OptIn(SymbolInternals::class) - context(context: CheckerContext) - private fun symbolInformation( - firBasedSymbol: FirBasedSymbol<*>?, - symbol: Symbol, - element: KtSourceElement, - ): SymbolInformation { - val supers = - when (firBasedSymbol) { - is FirClassSymbol -> - firBasedSymbol.resolvedSuperTypeRefs - .filter { it !is FirImplicitAnyTypeRef } - .mapNotNull { it.toClassLikeSymbol(firBasedSymbol.moduleData.session) } - .flatMap { cache[it] } - is FirFunctionSymbol<*> -> - firBasedSymbol.directOverriddenSymbolsSafe().flatMap { cache[it] } - else -> emptyList() - } - return symbolInformation { - this.symbol = symbol.toString() - this.displayName = - if (firBasedSymbol != null) displayName(firBasedSymbol) else element.text.toString() - if (firBasedSymbol != null) { - renderSignature(firBasedSymbol.fir)?.let { rendered -> - signatureDocumentation = signature { - language = "kotlin" - text = rendered - } - } - 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() - isImplementation = true - } - } - } - } - - private fun occurrence( - symbol: Symbol, - element: KtSourceElement, - isDefinition: Boolean, - enclosingSource: KtSourceElement?, - ): Occurrence { - val builder = Occurrence.newBuilder().setSymbol(symbol.toString()) - if (isDefinition) builder.setSymbolRoles(SymbolRole.Definition.number) - val range = range(element) - if (range.isSingleLine) builder.singleLineRange = range.toSingleLineRange() - else builder.multiLineRange = range.toMultiLineRange() - if (enclosingSource != null) { - val enclosingRange = enclosingRange(enclosingSource) - if (enclosingRange.isSingleLine) { - builder.singleLineEnclosingRange = enclosingRange.toSingleLineRange() - } else { - builder.multiLineEnclosingRange = enclosingRange.toMultiLineRange() - } - } - return builder.build() - } - - private fun range(element: KtSourceElement): ScipRange { - val line = lineMap.lineNumber(element) - 1 - val startCol = lineMap.startCharacter(element) - val endCol = lineMap.endCharacter(element) - return ScipRange.singleLine(line, startCol, endCol) - } - - private fun enclosingRange(element: KtSourceElement): ScipRange { - val startLine = lineMap.lineNumber(element) - 1 - val startCol = lineMap.startCharacter(element) - val endLine = lineMap.lineNumberForOffset(element.endOffset) - 1 - val endCol = lineMap.columnForOffset(element.endOffset) - return ScipRange.range(startLine, startCol, endLine, endCol) - } - - private fun relativePath(): String = - ScipShardPaths.relativePath(sourceroot, Paths.get(file.path)) - - /** - * Renders [element] as a Kotlin signature using [FirRenderer]'s readability preset, with kdoc - * stripped (kdoc is exposed separately via [SymbolInformation.documentation]). - */ - private fun renderSignature(element: FirElement): String? { - val renderer = - FirRenderer( - typeRenderer = ConeTypeRenderer(), - idRenderer = ConeIdShortRenderer(), - classMemberRenderer = FirNoClassMemberRenderer(), - bodyRenderer = null, - propertyAccessorRenderer = null, - callArgumentsRenderer = FirCallNoArgumentsRenderer(), - modifierRenderer = FirAllModifierRenderer(FirModifierRenderer.StaticPolicy.Default), - callableSignatureRenderer = FirCallableSignatureRendererForReadability(), - declarationRenderer = FirDeclarationRenderer("local "), - ) - val rendered = renderer.renderElementAsString(element) - return if (rendered.isEmpty()) null else rendered - } - - private fun docComment(element: FirElement): String? { - val kdoc = element.source?.getChild(KtTokens.DOC_COMMENT)?.text?.toString() ?: return null - 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 - val out = StringBuilder() - var first = true - kdoc.lineSequence().forEach { line -> - if (line.isEmpty()) return@forEach - var start = 0 - while (start < line.length && line[start].isWhitespace()) start++ - if (start < line.length && line[start] == '/') start++ - while (start < line.length && line[start] == '*') start++ - var end = line.length - 1 - if (end > start && line[end] == '/') end-- - while (end > start && line[end] == '*') end-- - while (end > start && line[end].isWhitespace()) end-- - start = minOf(start, line.length - 1) - if (end > start) end++ - if (!first) out.append('\n') - out.append(line, start, end) - first = false - } - return out.toString().trim() - } - - companion object { - @OptIn(SymbolInternals::class, RenderingInternals::class) - private fun displayName(firBasedSymbol: FirBasedSymbol<*>): String = - when (firBasedSymbol) { - is FirClassLikeSymbol -> firBasedSymbol.classId.shortClassName.asString() - is FirPropertyAccessorSymbol -> firBasedSymbol.fir.propertySymbol.name.asString() - is FirFunctionSymbol -> 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 deleted file mode 100644 index 5cfbe6c5a..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt +++ /dev/null @@ -1,191 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.nio.file.Path -import org.jetbrains.kotlin.KtSourceElement -import org.jetbrains.kotlin.KtSourceFile -import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.declarations.* -import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference -import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol -import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol -import org.jetbrains.kotlin.name.FqName -import org.scip_code.scip.Document - -/** - * Per-file accumulator of SCIP occurrences and symbols. The FIR checkers in [AnalyzerCheckers] call - * into this and the resulting [Document] is written as a `.scip` shard at the end of compilation. - */ -class ScipVisitor( - sourceroot: Path, - file: KtSourceFile, - lineMap: LineMap, - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache = LocalSymbolsCache(), -) { - private val cache = SymbolsCache(globals, locals) - private val documentBuilder = ScipTextDocumentBuilder(sourceroot, file, lineMap, cache) - - private data class SymbolDescriptorPair( - val firBasedSymbol: FirBasedSymbol<*>?, - val symbol: Symbol, - ) - - fun build(): Document = documentBuilder.build() - - context(context: CheckerContext) - private fun Sequence?.emitAll( - element: KtSourceElement, - isDefinition: Boolean, - enclosingSource: KtSourceElement? = null, - ): List? = - this?.onEach { (firBasedSymbol, symbol) -> - documentBuilder.emitScipData( - firBasedSymbol, - symbol, - element, - isDefinition, - enclosingSource, - ) - } - ?.map { it.symbol } - ?.toList() - - private fun Sequence.with(firBasedSymbol: FirBasedSymbol<*>?) = - this.map { SymbolDescriptorPair(firBasedSymbol, it) } - - context(context: CheckerContext) - fun visitPackage(pkg: FqName, element: KtSourceElement) { - cache[pkg].with(null).emitAll(element, isDefinition = false) - } - - context(context: CheckerContext) - fun visitClassReference(firClassSymbol: FirClassLikeSymbol<*>, element: KtSourceElement) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false) - } - - 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, - enclosingSource: KtSourceElement? = null, - ) { - cache[firClass.symbol] - .with(firClass.symbol) - .emitAll(element, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitPrimaryConstructor( - firConstructor: FirConstructor, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firConstructor.symbol] - .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitSecondaryConstructor( - firConstructor: FirConstructor, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firConstructor.symbol] - .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitNamedFunction( - firFunction: FirFunction, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firFunction.symbol] - .with(firFunction.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitProperty( - firProperty: FirProperty, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firProperty.symbol] - .with(firProperty.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitParameter( - firParameter: FirValueParameter, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firParameter.symbol] - .with(firParameter.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitTypeParameter( - firTypeParameter: FirTypeParameter, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firTypeParameter.symbol] - .with(firTypeParameter.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitTypeAlias( - firTypeAlias: FirTypeAlias, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firTypeAlias.symbol] - .with(firTypeAlias.symbol) - .emitAll(source, isDefinition = true, enclosingSource) - } - - context(context: CheckerContext) - fun visitPropertyAccessor( - firPropertyAccessor: FirPropertyAccessor, - source: KtSourceElement, - enclosingSource: KtSourceElement? = null, - ) { - cache[firPropertyAccessor.symbol] - .with(firPropertyAccessor.symbol) - .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, - ) { - cache[firResolvedNamedReference.resolvedSymbol] - .with(firResolvedNamedReference.resolvedSymbol) - .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 deleted file mode 100644 index 7ba63f961..000000000 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ /dev/null @@ -1,271 +0,0 @@ -package org.scip_code.scip_java.kotlinc - -import java.lang.System.err -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalDeclaredInBlock -import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess -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.scip_code.scip_java.kotlinc.ScipSymbolDescriptor.Kind -import org.scip_code.scip_java.shared.LocalSymbolsCache as SharedLocalSymbolsCache - -class GlobalSymbolsCache(testing: Boolean = false) : Iterable { - private val globals = - if (testing) LinkedHashMap, Symbol>() - else HashMap, Symbol>() - private val packages = - if (testing) LinkedHashMap() else HashMap() - - operator fun get(symbol: FirBasedSymbol<*>, locals: LocalSymbolsCache): Sequence = - sequence { - emitSymbols(symbol, locals) - } - - operator fun get(symbol: FqName): Sequence = sequence { emitSymbols(symbol) } - - /** - * called whenever a new symbol should be yielded in the sequence e.g. for properties we also - * want to yield for every implicit getter/setter, but wouldn't want to yield for e.g. the - * package symbol parts that a class symbol is composed of. - */ - @OptIn(SymbolInternals::class) - private suspend fun SequenceScope.emitSymbols( - symbol: FirBasedSymbol<*>, - locals: LocalSymbolsCache, - ) { - yield(getSymbol(symbol, locals)) - if (symbol is FirPropertySymbol) { - if (symbol.fir.getter?.origin is FirDeclarationOrigin.Synthetic) - emitSymbols(symbol.fir.getter!!.symbol, locals) - if (symbol.fir.setter?.origin is FirDeclarationOrigin.Synthetic) - emitSymbols(symbol.fir.setter!!.symbol, locals) - } - } - - private suspend fun SequenceScope.emitSymbols(symbol: FqName) { - yield(getSymbol(symbol)) - } - - /** - * Entrypoint for building or looking-up a symbol without yielding a value in the sequence. - * Called recursively for every part of a symbol, unless a cached result short circuits. - */ - private fun getSymbol(symbol: FirBasedSymbol<*>, locals: LocalSymbolsCache): Symbol { - globals[symbol]?.let { - return it - } - locals[symbol]?.let { - return it - } - return uncachedSymbol(symbol, locals).also { if (it.isGlobal()) globals[symbol] = it } - } - - private fun getSymbol(symbol: FqName): Symbol { - packages[symbol]?.let { - return it - } - return uncachedSymbol(symbol).also { if (it.isGlobal()) packages[symbol] = it } - } - - @OptIn(SymbolInternals::class) - private fun uncachedSymbol(symbol: FirBasedSymbol<*>?, locals: LocalSymbolsCache): Symbol { - if (symbol == null || symbol is FirAnonymousFunctionSymbol) return Symbol.NONE - - if (symbol.fir.isLocalDeclaredInBlock) return locals + symbol - - val owner = getParentSymbol(symbol, locals) - - if (owner.isLocal() || owner == Symbol.NONE) return locals + symbol - - val scipDescriptor = scipDescriptor(symbol) - - return Symbol.createGlobal(owner, scipDescriptor) - } - - private fun uncachedSymbol(symbol: FqName): Symbol { - if (symbol.isRoot) return Symbol.ROOT_PACKAGE - - val owner = this.getSymbol(symbol.parent()) - return Symbol.createGlobal( - owner, - ScipSymbolDescriptor(Kind.PACKAGE, symbol.shortName().asString()), - ) - } - - /** - * Returns the parent DeclarationDescriptor for a given DeclarationDescriptor. For most - * descriptor types, this simply returns the 'containing' descriptor. For Module- or - * PackageFragmentDescriptors, it returns the descriptor for the parent fqName of the current - * descriptors fqName e.g. for the fqName `test.sample.main`, the parent fqName would be - * `test.sample`. - */ - @OptIn(SymbolInternals::class) - private fun getParentSymbol(symbol: FirBasedSymbol<*>, locals: LocalSymbolsCache): Symbol { - when (symbol) { - is FirTypeParameterSymbol -> - 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 - 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 - return symbol.getContainingDeclaration(session)?.let { getSymbol(it, locals) } - ?: getSymbol(symbol.packageFqName()) - } - is FirFileSymbol -> { - return getSymbol(symbol.fir.packageFqName) - } - else -> return Symbol.NONE - } - } - - @OptIn(SymbolInternals::class) - private fun scipDescriptor(symbol: FirBasedSymbol<*>): ScipSymbolDescriptor { - return when { - symbol is FirAnonymousObjectSymbol -> - symbol.source?.let { source -> - ScipSymbolDescriptor(Kind.TYPE, "") - } ?: ScipSymbolDescriptor.NONE - symbol is FirClassLikeSymbol -> - ScipSymbolDescriptor(Kind.TYPE, symbol.classId.shortClassName.asString()) - symbol is FirPropertyAccessorSymbol && symbol.isSetter -> - ScipSymbolDescriptor(Kind.METHOD, "set") - symbol is FirPropertyAccessorSymbol && symbol.isGetter -> - ScipSymbolDescriptor(Kind.METHOD, "get") - symbol is FirConstructorSymbol -> - ScipSymbolDescriptor(Kind.METHOD, "", methodDisambiguator(symbol)) - symbol is FirFunctionSymbol -> - ScipSymbolDescriptor( - Kind.METHOD, - symbol.name.toString(), - methodDisambiguator(symbol), - ) - symbol is FirTypeParameterSymbol -> - ScipSymbolDescriptor(Kind.TYPE_PARAMETER, symbol.name.toString()) - 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 - } - } - } - - @OptIn(SymbolInternals::class, DirectDeclarationsAccess::class) - private fun methodDisambiguator(symbol: FirFunctionSymbol<*>): String { - val session = symbol.moduleData.session - - val siblings = - when (val containingSymbol = symbol.getContainingSymbol(session)) { - 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 "()" - } - - var count = 0 - var found = false - for (decl in siblings) { - if (decl == symbol) { - found = true - break - } - - if (decl.memberDeclarationNameOrNull?.equals(symbol.name) == true) { - count++ - } - } - - if (!found) { - err.println("methodDisambiguator: ${symbol.callableId} not found in sibling list") - return "()" - } - if (count == 0) return "()" - return "(+${count})" - } - - override fun iterator(): Iterator = globals.values.iterator() -} - -typealias LocalSymbolsCache = SharedLocalSymbolsCache, Symbol> - -@Suppress("FunctionName") -fun LocalSymbolsCache(): LocalSymbolsCache = - SharedLocalSymbolsCache(HashMap()) { Symbol.createLocal(it) } - -operator fun LocalSymbolsCache.plus(symbol: FirBasedSymbol<*>): Symbol = put(symbol) - -class SymbolsCache(private val globals: GlobalSymbolsCache, private val locals: LocalSymbolsCache) { - operator fun get(symbol: FirBasedSymbol<*>) = globals[symbol, locals] - - operator fun get(symbol: FqName) = globals[symbol] -} diff --git a/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor deleted file mode 100644 index 13fc07284..000000000 --- a/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +++ /dev/null @@ -1 +0,0 @@ -org.scip_code.scip_java.kotlinc.AnalyzerCommandLineProcessor diff --git a/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar deleted file mode 100644 index 64d92f95c..000000000 --- a/scip-kotlinc/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +++ /dev/null @@ -1 +0,0 @@ -org.scip_code.scip_java.kotlinc.AnalyzerRegistrar 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 deleted file mode 100644 index 4f77e9a82..000000000 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ /dev/null @@ -1,3040 +0,0 @@ -package org.scip_code.scip_java.kotlinc.test - -import com.tschuchort.compiletesting.KotlinCompilation -import com.tschuchort.compiletesting.PluginOption -import com.tschuchort.compiletesting.SourceFile -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 -import kotlin.test.assertEquals -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) -class AnalyzerTest { - fun compileScip(path: Path, @Language("kotlin") code: String): Document { - val buildPath = File(path.resolve("build").toString()).apply { mkdir() } - val source = SourceFile.testKt(code) - lateinit var document: Document - - val result = - KotlinCompilation() - .apply { - sources = listOf(source) - compilerPluginRegistrars = listOf(AnalyzerRegistrar { document = it }) - 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 - document shouldNotBe null - return document - } - - @Test - fun `basic 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 - } - }, - ) - 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) - } - - @Test - fun imports(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - import kotlin.Boolean - import kotlin.Int as KInt - """, - ) - - val occurrences = - arrayOf( - scipOccurrence { - role = REFERENCE - symbol = "sample/" - range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 14 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "kotlin/" - range { - startLine = 2 - startCharacter = 7 - endLine = 2 - endCharacter = 13 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "kotlin/Boolean#" - range { - startLine = 2 - startCharacter = 14 - endLine = 2 - endCharacter = 21 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "kotlin/" - range { - startLine = 3 - startCharacter = 7 - endLine = 3 - endCharacter = 13 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "kotlin/Int#" - range { - startLine = 3 - startCharacter = 14 - endLine = 3 - endCharacter = 17 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - } - - @Test - fun `local classes`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - fun foo() { - class LocalClass { - fun localClassMethod() {} - } - } - """, - ) - - val occurrences = - arrayOf( - scipOccurrence { - role = DEFINITION - symbol = "sample/foo()." - range { - startLine = 2 - startCharacter = 4 - endLine = 2 - endCharacter = 7 - } - enclosingRange { - startLine = 2 - endLine = 6 - endCharacter = 1 - } - }, - // LocalClass - scipOccurrence { - role = DEFINITION - symbol = "local 0" - range { - startLine = 3 - startCharacter = 8 - endLine = 3 - endCharacter = 18 - } - enclosingRange { - startLine = 3 - startCharacter = 2 - endLine = 5 - endCharacter = 3 - } - }, - // LocalClass constructor - scipOccurrence { - role = DEFINITION - symbol = "local 1" - range { - startLine = 3 - startCharacter = 8 - endLine = 3 - endCharacter = 18 - } - enclosingRange { - startLine = 3 - startCharacter = 2 - endLine = 5 - endCharacter = 3 - } - }, - // localClassMethod - scipOccurrence { - role = DEFINITION - symbol = "local 2" - range { - startLine = 4 - startCharacter = 8 - endLine = 4 - endCharacter = 24 - } - enclosingRange { - startLine = 4 - startCharacter = 4 - endLine = 4 - endCharacter = 29 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - - val symbols = - 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" - }, - ) - document.symbolsList.shouldContainAll(*symbols) - } - - @Test - fun `lambda parameters`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - fun use() { - val f = { n: Int -> n * 2 } - } - """, - ) - - val occurrences = - arrayOf( - // val f is a local variable — gets local0 via isLocalMember - scipOccurrence { - role = DEFINITION - symbol = "local 0" - range { - startLine = 3 - startCharacter = 8 - endLine = 3 - endCharacter = 9 - } - enclosingRange { - startLine = 3 - startCharacter = 4 - endLine = 3 - 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 = "local 1" - range { - startLine = 3 - startCharacter = 14 - endLine = 3 - endCharacter = 15 - } - enclosingRange { - startLine = 3 - startCharacter = 14 - endLine = 3 - endCharacter = 20 - } - }, - // explicit type annotation on n emits a class reference - scipOccurrence { - role = REFERENCE - symbol = "kotlin/Int#" - range { - startLine = 3 - startCharacter = 17 - endLine = 3 - endCharacter = 20 - } - }, - // reference to n in the lambda body uses the same local symbol - scipOccurrence { - role = REFERENCE - symbol = "local 1" - range { - startLine = 3 - startCharacter = 24 - endLine = 3 - endCharacter = 25 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - - val symbols = - arrayOf( - scipSymbol { - symbol = "sample/use()." - kind = Kind.Method - enclosingSymbol = "sample/" - displayName = "use" - signatureText = "public final fun use(): Unit" - }, - scipSymbol { - symbol = "local 0" - kind = Kind.Property - enclosingSymbol = "sample/use()." - displayName = "f" - signatureText = "local val f: (Int) -> Int" - }, - scipSymbol { - symbol = "local 1" - kind = Kind.Parameter - displayName = "n" - signatureText = "n: Int" - }, - ) - document.symbolsList.shouldContainAll(*symbols) - } - - @Test - fun `local functions`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - fun outer() { - fun inner() {} - fun innerWithReturnType(): Int = 42 - inner() - } - """, - ) - - val occurrences = - arrayOf( - scipOccurrence { - role = DEFINITION - symbol = "sample/outer()." - range { - startLine = 2 - startCharacter = 4 - endLine = 2 - endCharacter = 9 - } - enclosingRange { - startLine = 2 - startCharacter = 0 - endLine = 6 - endCharacter = 1 - } - }, - // inner() — local named function gets a local symbol - scipOccurrence { - role = DEFINITION - symbol = "local 0" - range { - startLine = 3 - startCharacter = 8 - endLine = 3 - endCharacter = 13 - } - enclosingRange { - startLine = 3 - startCharacter = 4 - endLine = 3 - endCharacter = 18 - } - }, - // innerWithReturnType() — local named function with explicit return type - scipOccurrence { - role = DEFINITION - symbol = "local 1" - range { - startLine = 4 - startCharacter = 8 - endLine = 4 - endCharacter = 27 - } - enclosingRange { - startLine = 4 - startCharacter = 4 - endLine = 4 - endCharacter = 39 - } - }, - // Int return-type reference - scipOccurrence { - role = REFERENCE - symbol = "kotlin/Int#" - range { - startLine = 4 - startCharacter = 31 - endLine = 4 - endCharacter = 34 - } - }, - // call site inner() references the same local symbol - scipOccurrence { - role = REFERENCE - symbol = "local 0" - range { - 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/bar()." - range { - startLine = 4 - startCharacter = 4 - endLine = 4 - endCharacter = 7 - } - enclosingRange { - startLine = 4 - startCharacter = 0 - endLine = 4 - endCharacter = 30 - } - }, - // MyClass in the return type position generates a class reference - scipOccurrence { - role = REFERENCE - symbol = "sample/MyClass#" - range { - startLine = 4 - startCharacter = 11 - endLine = 4 - endCharacter = 18 - } - }, - ) - 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/x." - range { - startLine = 3 - startCharacter = 4 - endLine = 3 - endCharacter = 5 - } - enclosingRange { - startLine = 3 - startCharacter = 0 - endLine = 3 - endCharacter = 19 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - - val symbols = - arrayOf( - scipSymbol { - symbol = "sample/MyAlias#" - kind = Kind.TypeAlias - enclosingSymbol = "sample/" - displayName = "MyAlias" - signatureText = "public final typealias MyAlias = Int\n" - } - ) - document.symbolsList.shouldContainAll(*symbols) - } - - @Test - fun `type parameters`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - fun identity(x: T): T = x - """, - ) - - val occurrences = - arrayOf( - scipOccurrence { - role = DEFINITION - symbol = "sample/identity()." - range { - startLine = 2 - startCharacter = 8 - endLine = 2 - endCharacter = 16 - } - enclosingRange { - startLine = 2 - startCharacter = 0 - endLine = 2 - endCharacter = 29 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/identity().[T]" - range { - startLine = 2 - startCharacter = 5 - endLine = 2 - endCharacter = 6 - } - enclosingRange { - startLine = 2 - startCharacter = 5 - endLine = 2 - 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 = DEFINITION - symbol = "sample/Box#" - range { - startLine = 2 - startCharacter = 6 - endLine = 2 - endCharacter = 9 - } - enclosingRange { - startLine = 2 - endLine = 4 - endCharacter = 1 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/Box#[T]" - range { - startLine = 2 - startCharacter = 10 - endLine = 2 - 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 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - } - - @Test - fun overrides(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - interface Interface { - fun foo() - } - - class Class : Interface { - override fun foo() {} - } - """, - ) - - 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 - } - }, - 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 = "sample/Interface#" - range { - 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) - - 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 - fun `anonymous object`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - interface Interface { - fun foo() - } - - fun main() { - val a = object : Interface { - override fun foo() {} - } - val b = object : Interface { - override fun foo() {} - } - } - """, - ) - - 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 - } - }, - 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 - - /* - 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) - } - - @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 - * - **/ - fun docstrings(msg: String): Int { return msg.length } - """ - .trimIndent(), - ) - document.assertDocumentation("sample/Docstrings#", "Example class docstring") - document.assertDocumentation("sample/docstrings().", "Example method docstring") - } - - @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 - - fun String.foo(): Int = 42 - fun use(s: String) = s.foo() - """, - ) - - 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) - - val symbols = - arrayOf( - scipSymbol { - symbol = "sample/String#foo()." - kind = Kind.Method - enclosingSymbol = "sample/" - displayName = "foo" - signatureText = "public final fun String.foo(): Int" - } - ) - document.symbolsList.shouldContainAll(*symbols) - } - - @Test - fun `extension property receiver type reference`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - val Int.asString: String get() = this.toString() - fun use() = 42.asString - """, - ) - - 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) - - val symbols = - arrayOf( - scipSymbol { - symbol = "sample/Int#asString." - kind = Kind.Property - enclosingSymbol = "sample/" - displayName = "asString" - signatureText = "public final val Int.asString: String" - } - ) - document.symbolsList.shouldContainAll(*symbols) - } - - @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() {} - } - fun MyClass.foo(x: Int) {} - """, - ) - - 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) - } - - @Test - fun `enum entry definitions`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - enum class Color { RED, GREEN, BLUE } - - fun useEnum(): Color = Color.RED - """, - ) - - 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) - - 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) - } - - @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 - }; - - 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) - - 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) - } - - @Test - fun `named object declarations`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - object MySingleton { - fun hello(): String = "hi" - } - fun use() = MySingleton.hello() - """, - ) - - 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) - - 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) - } - - @Test - fun `companion object`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - class Foo { - companion object Factory { - fun create(): Foo = Foo() - } - } - fun use() = Foo.Factory.create() - """, - ) - - 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) - - 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) - } - - @Test - fun `unnamed companion object`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - 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 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 `string template references`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - package sample - - fun greet(name: String) = "Hello, ${'$'}name!" - """, - ) - - val occurrences = - arrayOf( - scipOccurrence { - role = DEFINITION - symbol = "sample/greet()." - range { - 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 = "sample/greet().(name)" - range { - 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 = "sample/Person#" - range { - startLine = 4 - startCharacter = 6 - endLine = 4 - endCharacter = 12 - } - enclosingRange { - startLine = 4 - startCharacter = 0 - endLine = 4 - endCharacter = 31 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "sample/Named#" - range { - startLine = 4 - startCharacter = 15 - endLine = 4 - endCharacter = 20 - } - }, - 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 = "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 `three-way overload disambiguator`(@TempDir path: Path) { - val document = - compileScip( - path, - """ - 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 = DEFINITION - symbol = "sample/add()." - range { - startLine = 2 - startCharacter = 4 - endLine = 2 - endCharacter = 7 - } - enclosingRange { - startLine = 2 - startCharacter = 0 - endLine = 2 - endCharacter = 36 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/add(+1)." - range { - startLine = 3 - startCharacter = 4 - endLine = 3 - endCharacter = 7 - } - enclosingRange { - startLine = 3 - startCharacter = 0 - endLine = 3 - endCharacter = 45 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "sample/add(+2)." - range { - startLine = 4 - startCharacter = 4 - endLine = 4 - endCharacter = 7 - } - enclosingRange { - startLine = 4 - startCharacter = 0 - endLine = 4 - endCharacter = 45 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "sample/add()." - range { - startLine = 6 - startCharacter = 4 - endLine = 6 - endCharacter = 7 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "sample/add(+1)." - range { - startLine = 7 - startCharacter = 4 - endLine = 7 - endCharacter = 7 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "sample/add(+2)." - range { - startLine = 8 - startCharacter = 4 - endLine = 8 - endCharacter = 7 - } - }, - ) - document.occurrencesList.shouldContainAll(*occurrences) - - val symbols = - arrayOf( - scipSymbol { - symbol = "sample/add()." - kind = Kind.Method - enclosingSymbol = "sample/" - displayName = "add" - signatureText = "public final fun add(x: Int, y: Int): Int" - }, - scipSymbol { - 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) - } - - private fun Document.assertDocumentation(symbol: String, expectedDocumentation: String) { - val info = - this.symbolsList.find { it.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 deleted file mode 100644 index fe8bdebc7..000000000 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.scip_code.scip_java.kotlinc.test - -import org.scip_code.scip.Occurrence -import org.scip_code.scip.SymbolInformation -import org.scip_code.scip.SymbolRole -import org.scip_code.scip.relationship -import org.scip_code.scip.signature -import org.scip_code.scip.symbolInformation -import org.scip_code.scip_java.shared.ScipRange - -/** - * Tiny DSL for building SCIP [Occurrence] / [SymbolInformation] test fixtures with the same shape - * as the original SCIP-based one used by the Kotlin tests. - * - *

Example: - * ``` - * scipOccurrence { - * role = DEFINITION - * symbol = "sample/Banana#" - * range { startLine = 1; startCharacter = 6; endLine = 1; endCharacter = 12 } - * enclosingRange { startLine = 1; endLine = 3; endCharacter = 1 } - * } - * ``` - */ -internal val REFERENCE: Int = SymbolRole.UnspecifiedSymbolRole.number -internal val DEFINITION: Int = SymbolRole.Definition.number - -@DslMarker annotation class ScipBuilderDsl - -@ScipBuilderDsl -class ScipRangeBuilder { - var startLine: Int = 0 - var startCharacter: Int = 0 - /** - * Default sentinel: when [endLine] is left untouched, the produced range is single-line at - * [startLine]. - */ - var endLine: Int = -1 - var endCharacter: Int = 0 - - internal fun toScipRange(): ScipRange { - val line = if (endLine < 0) startLine else endLine - return ScipRange.range(startLine, startCharacter, line, endCharacter) - } -} - -@ScipBuilderDsl -class ScipOccurrenceBuilder { - var role: Int = REFERENCE - var symbol: String = "" - private var range: ScipRange? = null - private var enclosingRange: ScipRange? = null - - fun range(block: ScipRangeBuilder.() -> Unit) { - range = ScipRangeBuilder().apply(block).toScipRange() - } - - fun enclosingRange(block: ScipRangeBuilder.() -> Unit) { - enclosingRange = ScipRangeBuilder().apply(block).toScipRange() - } - - internal fun build(): Occurrence { - val builder = Occurrence.newBuilder().setSymbol(symbol).setSymbolRoles(role) - range?.let { - if (it.isSingleLine) builder.singleLineRange = it.toSingleLineRange() - else builder.multiLineRange = it.toMultiLineRange() - } - enclosingRange?.let { - if (it.isSingleLine) builder.singleLineEnclosingRange = it.toSingleLineRange() - else builder.multiLineEnclosingRange = it.toMultiLineRange() - } - return builder.build() - } -} - -@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() - private val overrides = mutableListOf() - - fun documentation(text: String) { - docs += text - } - - /** - * Appends an `is_implementation` [Relationship]. Mirrors the old SCIP-flavored - * `addOverriddenSymbols` so existing test fixtures port over with minimal diff. - */ - fun addOverriddenSymbols(vararg symbols: String) { - overrides.addAll(symbols) - } - - 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 - } - signatureText?.let { sigText -> - signatureDocumentation = signature { - language = "kotlin" - text = sigText - } - } - for (d in docs) documentation += d - for (s in overrides) { - relationships += relationship { - symbol = s - isImplementation = true - } - } - } -} - -internal fun scipOccurrence(block: ScipOccurrenceBuilder.() -> Unit): Occurrence = - ScipOccurrenceBuilder().apply(block).build() - -internal fun scipSymbol(block: ScipSymbolInformationBuilder.() -> Unit): SymbolInformation = - ScipSymbolInformationBuilder().apply(block).build() 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 deleted file mode 100644 index 0bc8349bf..000000000 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt +++ /dev/null @@ -1,798 +0,0 @@ -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 - -@ExperimentalCompilerApi -class ScipSymbolsTest { - @TestFactory - fun `method disambiguator`() = - listOf( - ExpectedSymbols( - "Basic two methods", - SourceFile.testKt( - """ - |class Test { - | fun sample() {} - | fun sample(x: Int) {} - |} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData( - listOf("Test#sample().".symbol(), "Test#sample(+1).".symbol()) - ), - ), - ExpectedSymbols( - "Inline class constructor", - SourceFile.testKt( - """ - |class Test(val x: Int) - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(listOf("Test#``().(x)".symbol())), - ), - ExpectedSymbols( - "Inline + secondary class constructors", - SourceFile.testKt( - """ - |class Test(val x: Int) { - | constructor(y: Long): this(y.toInt()) - | constructor(z: String): this(z.toInt()) - |} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData( - listOf( - "Test#``().(x)".symbol(), - "Test#``(+1).(y)".symbol(), - "Test#``(+2).(z)".symbol(), - ) - ), - ), - ExpectedSymbols( - "Disambiguator number is not affected by different named methods", - SourceFile.testKt( - """ - |class Test { - | fun sample() {} - | fun test() {} - | fun test(x: Int) {} - |} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData(listOf("Test#test().".symbol(), "Test#test(+1).".symbol())), - ), - ExpectedSymbols( - "Top level overloaded functions", - SourceFile.testKt( - """ - |fun test() {} - |fun test(x: Int) {} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData(listOf("test().".symbol(), "test(+1).(x)".symbol())), - ), - ExpectedSymbols( - "Annotations incl annotation type alias", - SourceFile.testKt( - """ - |import kotlin.contracts.ExperimentalContracts - |import kotlin.test.Test - | - |@ExperimentalContracts - |class Banaan { - | @Test - | fun test() {} - |} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData( - listOf( - "kotlin/contracts/ExperimentalContracts#".symbol(), - "kotlin/test/Test#".symbol(), - ) - ), - ), - // https://kotlinlang.slack.com/archives/C7L3JB43G/p1624995376114900 - /*ExpectedSymbols( - "Method call with type parameters", - SourceFile.testKt(""" - import org.junit.jupiter.api.io.TempDir - val burger = LinkedHashMap() - """), - symbolsCacheData = SymbolCacheData( - listOf("kotlin/collection/TypeAliasesKt#LinkedHashMap#``().".symbol()) - ) - )*/ - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `check package symbols`() = - listOf( - ExpectedSymbols( - "single component package name", - SourceFile.testKt( - """ - |package main - | - |class Test - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(listOf("main/Test#".symbol()), 0), - ), - ExpectedSymbols( - "multi component package name", - SourceFile.testKt( - """ - |package test.sample.main - | - |class Test - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(listOf("test/sample/main/Test#".symbol()), 0), - ), - ExpectedSymbols( - "no package name", - SourceFile.testKt( - """ - |class Test - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(listOf("Test#".symbol()), 0), - ), - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `check locals counts`() = - listOf( - ExpectedSymbols( - "simple variables", - SourceFile.testKt( - """ - |fun test() { - | val x = "hello" - | println(x) - |} - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(localsCount = 1), - ) - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `builtin symbols`() = - listOf( - ExpectedSymbols( - "types", - SourceFile.testKt( - """ - |var x: Int = 1 - |lateinit var y: Unit - |lateinit var z: Any - |lateinit var w: Nothing - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData( - listOf( - "kotlin/Int#".symbol(), - "kotlin/Unit#".symbol(), - "kotlin/Any#".symbol(), - "kotlin/Nothing#".symbol(), - ) - ), - ), - ExpectedSymbols( - "functions", - SourceFile.testKt( - """ - |val x = mapOf() - |fun main() { - | println() - |} - |""" - .trimMargin() - ), - symbolsCacheData = - SymbolCacheData( - listOf( - "kotlin/collections/mapOf(+2).".symbol(), - "kotlin/io/println(+10).".symbol(), - ) - ), - ), - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `reference expressions`() = - listOf( - ExpectedSymbols( - "dot qualified expression", - SourceFile.testKt( - """ - |import java.lang.System - | - |fun main() { - | System.err - |} - |""" - .trimMargin() - ), - symbolsCacheData = SymbolCacheData(listOf("java/lang/System#err.".symbol())), - ) - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `properties with getters-setters`() = - listOf( - ExpectedSymbols( - "top level properties - implicit", - SourceFile.testKt( - """ - |var x: Int = 5 - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "x." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { endCharacter = 14 } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.get()." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { endCharacter = 14 } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.set()." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { endCharacter = 14 } - }, - ) - ), - ), - ExpectedSymbols( - "top level properties - explicit getter", - SourceFile.testKt( - """ - |var x: Int = 5 - | get() = field + 10 - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "x." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { - endLine = 1 - endCharacter = 22 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.set()." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { - endLine = 1 - endCharacter = 22 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.get()." - range { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 7 - } - enclosingRange { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 22 - } - }, - ) - ), - ), - ExpectedSymbols( - "top level properties - explicit setter", - SourceFile.testKt( - """ - |var x: Int = 5 - | set(value) { field = value + 5 } - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "x." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { - endLine = 1 - endCharacter = 36 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.get()." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { - endLine = 1 - endCharacter = 36 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.set()." - range { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 7 - } - enclosingRange { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 36 - } - }, - ) - ), - ), - ExpectedSymbols( - "top level properties - explicit getter & setter", - SourceFile.testKt( - """ - |var x: Int = 5 - | get() = field + 10 - | set(value) { field = value + 10 } - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "x." - range { - startLine = 0 - startCharacter = 4 - endLine = 0 - endCharacter = 5 - } - enclosingRange { - endLine = 2 - endCharacter = 37 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.get()." - range { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 7 - } - enclosingRange { - startLine = 1 - startCharacter = 4 - endLine = 1 - endCharacter = 22 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "x.set()." - range { - startLine = 2 - startCharacter = 4 - endLine = 2 - endCharacter = 7 - } - enclosingRange { - startLine = 2 - startCharacter = 4 - endLine = 2 - endCharacter = 37 - } - }, - ) - ), - ), - ExpectedSymbols( - "class constructor properties", - SourceFile.testKt( - """ - |class Test(var sample: Int, text: String): Throwable(sample.toString()) { - | fun test() { - | println(sample) - | } - |} - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "Test#``().(sample)" - range { - startLine = 0 - startCharacter = 15 - endLine = 0 - endCharacter = 21 - } - enclosingRange { - startCharacter = 11 - endCharacter = 26 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "Test#sample." - range { - startLine = 0 - startCharacter = 15 - endLine = 0 - endCharacter = 21 - } - enclosingRange { - startCharacter = 11 - endCharacter = 26 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "Test#sample.get()." - range { - startLine = 0 - startCharacter = 15 - endLine = 0 - endCharacter = 21 - } - enclosingRange { - startCharacter = 11 - endCharacter = 26 - } - }, - scipOccurrence { - role = DEFINITION - symbol = "Test#sample.set()." - range { - startLine = 0 - startCharacter = 15 - endLine = 0 - endCharacter = 21 - } - enclosingRange { - startCharacter = 11 - endCharacter = 26 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "Test#``().(sample)" - range { - startLine = 0 - startCharacter = 53 - endLine = 0 - endCharacter = 59 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "Test#sample." - range { - startLine = 2 - startCharacter = 16 - endLine = 2 - endCharacter = 22 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "Test#sample.get()." - range { - startLine = 2 - startCharacter = 16 - endLine = 2 - endCharacter = 22 - } - }, - ) - ), - ), - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `class constructors`() = - listOf( - ExpectedSymbols( - "implicit primary constructor", - SourceFile.testKt( - """ - |class Banana - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "Banana#" - range { - startLine = 0 - startCharacter = 6 - endLine = 0 - endCharacter = 12 - } - enclosingRange { endCharacter = 12 } - }, - scipOccurrence { - role = DEFINITION - symbol = "Banana#``()." - range { - startLine = 0 - startCharacter = 6 - endLine = 0 - endCharacter = 12 - } - enclosingRange { endCharacter = 12 } - }, - ) - ), - ), - ExpectedSymbols( - "explicit primary constructor without keyword", - SourceFile.testKt( - """ - |class Banana(size: Int) - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "Banana#" - range { - startLine = 0 - startCharacter = 6 - endLine = 0 - endCharacter = 12 - } - enclosingRange { endCharacter = 23 } - }, - scipOccurrence { - role = DEFINITION - symbol = "Banana#``()." - range { - startLine = 0 - startCharacter = 6 - endLine = 0 - endCharacter = 12 - } - enclosingRange { - startCharacter = 12 - endCharacter = 23 - } - }, - ) - ), - ), - ExpectedSymbols( - "explicit primary constructor with keyword", - SourceFile.testKt( - """ - |class Banana constructor(size: Int) - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = DEFINITION - symbol = "Banana#" - range { - startLine = 0 - startCharacter = 6 - endLine = 0 - endCharacter = 12 - } - enclosingRange { endCharacter = 35 } - }, - scipOccurrence { - role = DEFINITION - symbol = "Banana#``()." - range { - startLine = 0 - startCharacter = 13 - endLine = 0 - endCharacter = 24 - } - enclosingRange { - startCharacter = 13 - endCharacter = 35 - } - }, - ) - ), - ), - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun `Single Abstract Method interface`() = - listOf( - ExpectedSymbols( - "basic java.lang.Runnable", - SourceFile.testKt( - """ - |val x = Runnable { }.run() - |""" - .trimMargin() - ), - scip = - ScipData( - expectedOccurrences = - listOf( - scipOccurrence { - role = REFERENCE - symbol = "java/lang/Runnable#" - range { - startLine = 0 - startCharacter = 8 - endLine = 0 - endCharacter = 16 - } - }, - scipOccurrence { - role = REFERENCE - symbol = "java/lang/Runnable#run()." - range { - startLine = 0 - startCharacter = 21 - endLine = 0 - endCharacter = 24 - } - }, - ) - ), - ) - ) - .mapCheckExpectedSymbols() - - @TestFactory - fun kdoc() = - listOf( - ExpectedSymbols( - "empty kdoc line", - SourceFile.testKt( - """ - |/** - | - |hello world - |* test content - |*/ - |val x = "" - |""" - .trimMargin() - ), - scip = - ScipData( - expectedSymbols = - 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 = "x.get()." - kind = Kind.Method - enclosingSymbol = "x." - displayName = "x" - signatureText = "public get(): String" - documentation("hello world\n test content") - }, - ) - ), - ) - ) - .mapCheckExpectedSymbols() -} 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 deleted file mode 100644 index 5622fe594..000000000 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt +++ /dev/null @@ -1,209 +0,0 @@ -package org.scip_code.scip_java.kotlinc.test - -import com.tschuchort.compiletesting.KotlinCompilation -import com.tschuchort.compiletesting.SourceFile -import io.kotest.assertions.assertSoftly -import io.kotest.assertions.throwables.shouldNotThrowAny -import io.kotest.matchers.collections.shouldContainInOrder -import io.kotest.matchers.shouldBe -import java.nio.file.Path -import java.nio.file.Paths -import org.intellij.lang.annotations.Language -import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar -import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi -import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.diagnostics.DiagnosticReporter -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind -import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFileChecker -import org.jetbrains.kotlin.fir.declarations.FirFile -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter -import org.junit.jupiter.api.Assumptions.assumeFalse -import org.junit.jupiter.api.DynamicTest -import org.junit.jupiter.api.DynamicTest.dynamicTest -import org.scip_code.scip.Document -import org.scip_code.scip.Occurrence -import org.scip_code.scip.SymbolInformation -import org.scip_code.scip_java.kotlinc.* -import org.scip_code.scip_java.kotlinc.AnalyzerCheckers.Companion.visitors -import org.scip_code.scip_java.shared.ScipOptions - -data class ExpectedSymbols( - val testName: String, - val source: SourceFile, - val symbolsCacheData: SymbolCacheData? = null, - val scip: ScipData? = null, -) { - /** Subset of a SCIP [Document] that a single test wants to assert on. */ - data class ScipData( - val expectedOccurrences: List? = null, - val expectedSymbols: List? = null, - ) - - data class SymbolCacheData( - val expectedGlobals: List? = null, - val localsCount: Int? = null, - ) -} - -fun SourceFile.Companion.testKt(@Language("kotlin") contents: String): SourceFile = - kotlin("Test.kt", contents) - -@ExperimentalCompilerApi -fun List.mapCheckExpectedSymbols(): List = - this.flatMap { (testName, source, symbolsData, scipData) -> - val globals = GlobalSymbolsCache(testing = true) - val locals = LocalSymbolsCache() - lateinit var document: Document - val compilation = configureTestCompiler(source, globals, locals) { document = it } - listOf( - dynamicTest("$testName - compilation") { - val result = shouldNotThrowAny { compilation.compile() } - result.exitCode shouldBe KotlinCompilation.ExitCode.OK - }, - dynamicTest("$testName - symbols") { - symbolsData?.apply { - println( - "checking symbols: ${expectedGlobals?.size ?: 0} globals and presence of $localsCount locals" - ) - checkContainsExpectedSymbols(globals, locals, expectedGlobals, localsCount) - } ?: assumeFalse(true) - }, - dynamicTest("$testName - scip") { - scipData?.apply { - println( - "checking scip: ${expectedOccurrences?.size ?: 0} occurrences and ${expectedSymbols?.size ?: 0} symbols" - ) - checkContainsExpectedScip(document, expectedOccurrences, expectedSymbols) - } ?: assumeFalse(true) - }, - ) - } - -fun checkContainsExpectedSymbols( - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache, - expectedGlobals: List?, - localsCount: Int? = null, -) { - assertSoftly(globals) { expectedGlobals?.let { this.shouldContainInOrder(it) } } - localsCount?.also { locals.size shouldBe it } -} - -fun checkContainsExpectedScip( - document: Document, - expectedOccurrences: List?, - expectedSymbols: List?, -) { - assertSoftly(document.occurrencesList) { - expectedOccurrences?.let { this.shouldContainInOrder(it) } - } - assertSoftly(document.symbolsList) { expectedSymbols?.let { this.shouldContainInOrder(it) } } -} - -@OptIn(ExperimentalCompilerApi::class) -private fun configureTestCompiler( - source: SourceFile, - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache, - hook: (Document) -> Unit = {}, -): KotlinCompilation { - val compilation = - KotlinCompilation().apply { - sources = listOf(source) - inheritClassPath = true - verbose = false - } - - val analyzer = scipVisitorAnalyzer(globals, locals, compilation.workingDir.toPath(), hook) - compilation.apply { compilerPluginRegistrars = listOf(analyzer) } - return compilation -} - -private class TestAnalyzerDeclarationCheckers( - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache, - sourceRoot: Path, -) : AnalyzerCheckers.AnalyzerDeclarationCheckers(sourceRoot) { - override val fileCheckers: Set = - setOf( - object : FirFileChecker(MppCheckerKind.Common) { - context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirFile) { - val ktFile = declaration.sourceFile ?: return - val lineMap = LineMap(declaration) - val visitor = ScipVisitor(sourceRoot, ktFile, lineMap, globals, locals) - visitors[ktFile] = visitor - } - }, - AnalyzerCheckers.SemanticImportsChecker(), - ) -} - -private class TestAnalyzerCheckers(session: FirSession) : AnalyzerCheckers(session) { - override val declarationCheckers: DeclarationCheckers - get() = - TestAnalyzerDeclarationCheckers( - session.testAnalyzerParamsProvider.globals, - session.testAnalyzerParamsProvider.locals, - session.testAnalyzerParamsProvider.sourceroot, - ) -} - -class TestAnalyzerParamsProvider( - session: FirSession, - var globals: GlobalSymbolsCache, - var locals: LocalSymbolsCache, - sourceroot: Path, -) : AnalyzerParamsProvider(session, ScipOptions().apply { this.sourceroot = sourceroot }) { - companion object { - fun getFactory( - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache, - sourceroot: Path, - ): Factory { - return Factory { TestAnalyzerParamsProvider(it, globals, locals, sourceroot) } - } - } -} - -val FirSession.testAnalyzerParamsProvider: TestAnalyzerParamsProvider by - FirSession.sessionComponentAccessor() - -@OptIn(ExperimentalCompilerApi::class) -fun scipVisitorAnalyzer( - globals: GlobalSymbolsCache, - locals: LocalSymbolsCache, - sourceroot: Path, - hook: (Document) -> Unit = {}, -): CompilerPluginRegistrar { - return object : CompilerPluginRegistrar() { - override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { - FirExtensionRegistrarAdapter.registerExtension( - object : FirExtensionRegistrar() { - override fun ExtensionRegistrarContext.configurePlugin() { - +TestAnalyzerParamsProvider.getFactory(globals, locals, sourceroot) - +::TestAnalyzerCheckers - } - } - ) - IrGenerationExtension.registerExtension( - PostAnalysisExtension( - configuration = configuration, - sourceRoot = sourceroot, - targetRoot = Paths.get(""), - callback = hook, - ) - ) - } - - override val pluginId = PLUGIN_ID - - override val supportsK2: Boolean - get() = true - } -} diff --git a/scip-snapshots/README.md b/scip-snapshots/README.md index ebd30fc3d..d51cece11 100644 --- a/scip-snapshots/README.md +++ b/scip-snapshots/README.md @@ -1,7 +1,7 @@ # SCIP snapshots This directory contains end-to-end snapshot fixtures for `scip-javac`, -`scip-kotlinc`, and future mixed Java/Kotlin cases. +`scip-kotlin-analysis`, and future mixed Java/Kotlin cases. ## Layout diff --git a/scip-snapshots/cases/kotlin/common/build.gradle.kts b/scip-snapshots/cases/kotlin/common/build.gradle.kts index 0dc573c2d..e3fc46602 100644 --- a/scip-snapshots/cases/kotlin/common/build.gradle.kts +++ b/scip-snapshots/cases/kotlin/common/build.gradle.kts @@ -1,9 +1,7 @@ import org.scip_code.scip_java.buildlogic.cleanDirectoryBeforeRunning import org.scip_code.scip_java.buildlogic.publishDirectoryArtifact -import org.scip_code.scip_java.buildlogic.scipKotlincPluginArgs import org.scip_code.scip_java.buildlogic.shadowJarArtifact import org.scip_code.scip_java.buildlogic.useScipJavac -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("scip.java-library") @@ -11,27 +9,66 @@ plugins { } val javacShadowJar = shadowJarArtifact(":scip-javac", "javacShadowJar") -val kotlincShadowJar = shadowJarArtifact(":scip-kotlinc", "kotlincShadowJar") dependencies { implementation(libs.kotlin.stdlib) } +// Runtime classpath of the Analysis-API-based Kotlin indexer. +val scipKotlinAnalysis: Configuration by configurations.creating { + isCanBeConsumed = false +} + +dependencies { + scipKotlinAnalysis(project(":scip-kotlin-analysis")) +} + val scipTargetroot = layout.buildDirectory.dir("scip-targetroot") val sourceroot = rootProject.rootDir.absolutePath val targetroot = scipTargetroot.get().asFile.absolutePath -tasks.named("compileKotlin") { - inputs.files(kotlincShadowJar) +// Kotlin sources are indexed by the standalone scip-kotlin-analysis indexer; +// compileKotlin only produces the classes that compileJava needs. +val scipIndexKotlin = tasks.register("scipIndexKotlin") { + classpath = scipKotlinAnalysis + mainClass.set("org.scip_code.scip_java.kotlin_analysis.MainKt") + val kotlinSources = layout.projectDirectory.dir("src/main/kotlin") + val compileClasspath = sourceSets.main.map { it.compileClasspath } + inputs.dir(kotlinSources) + inputs.files(compileClasspath) outputs.dir(scipTargetroot) - compilerOptions.freeCompilerArgs.addAll(scipKotlincPluginArgs(kotlincShadowJar.elements, sourceroot, targetroot)) cleanDirectoryBeforeRunning(scipTargetroot) + // Locals only: the argument provider must not capture the build script + // (configuration cache). + val sourcerootArg = sourceroot + val targetrootArg = scipTargetroot + val kotlinSourcesArg = kotlinSources.asFile.absolutePath + argumentProviders.add( + CommandLineArgumentProvider { + listOf( + "--sourceroot", + sourcerootArg, + "--targetroot", + targetrootArg.get().asFile.absolutePath, + "--classpath", + compileClasspath.get().asPath, + kotlinSourcesArg, + ) + } + ) } tasks.named("compileJava") { useScipJavac(rootDir, javacShadowJar, scipTargetroot) options.annotationProcessorPath = javacShadowJar options.compilerArgs.add("-Xplugin:scip -sourceroot:$sourceroot -targetroot:$targetroot") + // The Kotlin indexer cleans the targetroot before writing its shards; javac + // must add its shards afterwards. + mustRunAfter(scipIndexKotlin) +} + +tasks.named("classes") { + dependsOn(scipIndexKotlin) } publishDirectoryArtifact("scipTargetrootElements", scipTargetroot, tasks.named("classes")) diff --git a/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java b/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java new file mode 100644 index 000000000..f098207fb --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java @@ -0,0 +1,10 @@ +package snapshots; + +// No JDK classes: with --release, newer JDKs compile against ct.sym, which omits private +// constructors, so javac's overload numbers for them depend on the JDK running the build. +public class ConstructorsConsumer { + public static void run() { + new kotlin.text.Regex("a"); + new Overloaded(1); + } +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt new file mode 100644 index 000000000..72c2d7dce --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt @@ -0,0 +1,10 @@ +package snapshots + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +annotation class Tagged(val tag: String) + +@Tagged("service") +class AnnotatedService { + @Tagged("run") + fun run(): String = "running" +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt index 56cfa7270..99eea4429 100644 --- a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt @@ -6,3 +6,17 @@ class CompanionOwner { } fun create(): Int = CompanionOwner.create().hashCode() } + +class Foo { + companion object Factory { + fun create(): Foo = Foo() + } +} +fun useNamedCompanion() = Foo.Factory.create() + +class Bar { + companion object { + fun instance(): Bar = Bar() + } +} +fun useUnnamedCompanion() = Bar.instance() diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt new file mode 100644 index 000000000..8ddac0b79 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt @@ -0,0 +1,20 @@ +package snapshots + +class Overloaded(val s: String) { + constructor(i: Int) : this(i.toString()) +} + +typealias OverloadedAlias = Overloaded + +typealias StringIntMap = HashMap + +// `Regex` and `OverloadedAlias` reference the same symbols as ConstructorsConsumer.java. +fun constructors() { + // Library constructors are numbered like javac, private ones included. + java.io.File("a", "b") + Regex("a") + // Calls through type aliases reference the aliased class's constructor. + StringBuilder("x") + StringIntMap() + OverloadedAlias(1) +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt new file mode 100644 index 000000000..ca2646890 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt @@ -0,0 +1,10 @@ +package snapshots + +data class Point(val x: Int, val y: Int) + +fun destructure(): Int { + val (x, y) = Point(1, 2) + val labeled = listOf(Point(3, 4) to "label") + val total = labeled.sumOf { (point, label) -> point.x + label.length } + return x + y + total +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt new file mode 100644 index 000000000..fcb7189c5 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt @@ -0,0 +1,31 @@ +package snapshots + +enum class Suit(val symbol: Char) { + HEARTS('h'), + SPADES('s'); + + fun isRed(): Boolean = symbol == 'h' + + companion object { + fun fromSymbol(symbol: Char): Suit? = entries.find { it.symbol == symbol } + } +} + +fun describe(suit: Suit): String = + when (suit) { + Suit.HEARTS -> "red" + Suit.SPADES -> "black" + } + +enum class Color { RED, GREEN, BLUE } + +fun useEnum(): Color = Color.RED + +enum class Op { + // The entry body is an anonymous subclass; its override has a local symbol. + PLUS { + override fun apply(a: Int, b: Int) = a + b + }; + + abstract fun apply(a: Int, b: Int): Int +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt new file mode 100644 index 000000000..2664655b3 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt @@ -0,0 +1,14 @@ +package snapshots + +// Cross-package extensions belong to snapshots, not to the receiver's kotlin package. +fun String.foo(): Int = 42 +fun useExtension(s: String) = s.foo() + +val Int.asString: String get() = this.toString() +fun useExtensionProperty() = 42.asString + +class ExtensionReceiver { + fun foo() {} +} +// The extension is numbered after the receiver's member, so it gets foo(+1). +fun ExtensionReceiver.foo(x: Int) {} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt index ec545c48f..bc1cf9969 100644 --- a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt @@ -3,3 +3,19 @@ package snapshots fun sampleText(x: String = "") { println(x) } + +class MyClass + +fun bar(): MyClass = MyClass() + +fun greet(name: String) = "Hello, $name!" + +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 useOverloads() { + add(1, 2) + add(1.0, 2.0) + add("a", "b") +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt new file mode 100644 index 000000000..e0789c990 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt @@ -0,0 +1,25 @@ +package snapshots + +class Container>(private val items: MutableList) { + fun add(item: T): Container { + items.add(item) + return this + } + + fun mapItems(transform: (T) -> R?): List = items.mapNotNull(transform) +} + +fun firstOrSelf(items: List, fallback: T): T = items.firstOrNull() ?: fallback + +typealias StringContainer = Container + +fun useContainer(container: StringContainer): StringContainer = container.add("hello") + +typealias MyAlias = Int +val aliasValue: MyAlias = 42 + +fun identity(x: T): T = x + +class Box(val value: T) { + fun unwrap(): T = value +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt index 11ddd55d1..cb59b8833 100644 --- a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt @@ -25,3 +25,8 @@ class Seagull : Bird() { return "squawk" } } + +interface Named +interface Speakable +class Person : Named, Speakable +fun usePerson(p: Person): Named = p diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt index 445eeb10f..fcbd45caa 100644 --- a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt @@ -5,3 +5,7 @@ val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } val y = "fdsa".run { this.toByteArray() } val z = y.let { it.size } + +fun useLambda() { + val f = { n: Int -> n * 2 } +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt new file mode 100644 index 000000000..9189d63a1 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt @@ -0,0 +1,27 @@ +package snapshots + +fun localClasses() { + class LocalClass { + fun localClassMethod() {} + } +} + +interface Interface { + fun foo() +} + +fun anonymousObjects() { + // Each object and its members need distinct local symbols, with no INSTANCE field. + val a = object : Interface { + override fun foo() {} + } + val b = object : Interface { + override fun foo() {} + } +} + +fun outer() { + fun inner() {} + fun innerWithReturnType(): Int = 42 + inner() +} diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt index 5602cacc7..806c547ec 100644 --- a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt @@ -7,3 +7,9 @@ object ObjectKt { throw RuntimeException(message) } } + +// Named objects define an INSTANCE field for Java callers; object expressions do not. +object MySingleton { + fun hello(): String = "hi" +} +fun useSingleton() = MySingleton.hello() diff --git a/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt new file mode 100644 index 000000000..f49e34429 --- /dev/null +++ b/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt @@ -0,0 +1,16 @@ +package snapshots + +fun typeOperators(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 +} diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java new file mode 100644 index 000000000..ea5127a3c --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/java/snapshots/ConstructorsConsumer.java @@ -0,0 +1,34 @@ + package snapshots; + + // No JDK classes: with --release, newer JDKs compile against ct.sym, which omits private + // constructors, so javac's overload numbers for them depend on the JDK running the build. +//⌄ enclosing_range_start scip-java maven . . snapshots/ConstructorsConsumer# + public class ConstructorsConsumer { +// ^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/ConstructorsConsumer# +// kind Class +// display_name ConstructorsConsumer +// signature_documentation +// > public class ConstructorsConsumer +// ^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/ConstructorsConsumer#``(). +// kind Constructor +// display_name +// signature_documentation +// > public ConstructorsConsumer() +// ⌄ enclosing_range_start scip-java maven . . snapshots/ConstructorsConsumer#run(). + public static void run() { +// ^^^ definition scip-java maven . . snapshots/ConstructorsConsumer#run(). +// kind StaticMethod +// display_name run +// signature_documentation +// > public static void run() + new kotlin.text.Regex("a"); +// ^^^^^^ reference scip-java maven . . kotlin/ +// ^^^^ reference scip-java maven . . kotlin/text/ +// ^^^^^ reference scip-java maven . . kotlin/text/Regex#``(+1). + new Overloaded(1); +// ^^^^^^^^^^ reference scip-java maven . . snapshots/Overloaded#``(+1). + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/ConstructorsConsumer#run(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/ConstructorsConsumer# + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt new file mode 100644 index 000000000..019dec751 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Annotations.kt @@ -0,0 +1,79 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/Tagged# + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +// ^^^^^^ reference scip-java maven . . kotlin/annotation/Target# +// ^^^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/annotation/AnnotationTarget# +// ^^^^^ reference scip-java maven . . kotlin/annotation/AnnotationTarget#CLASS. +// ^^^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/annotation/AnnotationTarget# +// ^^^^^^^^ reference scip-java maven . . kotlin/annotation/AnnotationTarget#FUNCTION. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Tagged#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Tagged#``().(tag) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Tagged#tag. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Tagged#getTag(). + annotation class Tagged(val tag: String) +// ^^^^^^ definition scip-java maven . . snapshots/Tagged# +// kind Class +// display_name Tagged +// signature_documentation +// > @Target(...) public final annotation class Tagged : Annotation +// relationship scip-java maven . . kotlin/Annotation# implementation +// ^^^^^^ definition scip-java maven . . snapshots/Tagged#``(). +// kind Constructor +// display_name Tagged +// signature_documentation +// > public constructor(tag: String): Tagged +// ^^^ definition scip-java maven . . snapshots/Tagged#``().(tag) +// kind Parameter +// display_name tag +// signature_documentation +// > tag: String +// ^^^ definition scip-java maven . . snapshots/Tagged#tag. +// kind Property +// display_name tag +// signature_documentation +// > public final val tag: String +// ^^^ reference scip-java maven . . snapshots/Tagged#``().(tag) +// ^^^ definition scip-java maven . . snapshots/Tagged#getTag(). +// kind Method +// display_name tag +// signature_documentation +// > public get(): String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Tagged#``().(tag) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Tagged#tag. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Tagged#getTag(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Tagged# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Tagged#``(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/AnnotatedService# +//⌄ enclosing_range_start scip-java maven . . snapshots/AnnotatedService#``(). + @Tagged("service") +// ^^^^^^ reference scip-java maven . . snapshots/Tagged# + class AnnotatedService { +// ^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/AnnotatedService# +// kind Class +// display_name AnnotatedService +// signature_documentation +// > @Tagged(...) public final class AnnotatedService : Any +// ^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/AnnotatedService#``(). +// kind Constructor +// display_name AnnotatedService +// signature_documentation +// > public constructor(): AnnotatedService +// ⌄ enclosing_range_start scip-java maven . . snapshots/AnnotatedService#run(). + @Tagged("run") +// ^^^^^^ reference scip-java maven . . snapshots/Tagged# + fun run(): String = "running" +// ^^^ definition scip-java maven . . snapshots/AnnotatedService#run(). +// kind Method +// display_name run +// signature_documentation +// > @Tagged(...) public final fun run(): String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/AnnotatedService#run(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/AnnotatedService# +//⌃ enclosing_range_end scip-java maven . . snapshots/AnnotatedService#``(). + 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 288589564..9ec788702 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,9 +5,9 @@ // ⌄ 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#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#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#``().(apple) class Class constructor(private var banana: Int, apple: String) : // ^^^^^ definition scip-java maven . . snapshots/Class# @@ -32,17 +32,17 @@ // signature_documentation // > private final var banana: Int // ^^^^^^ reference scip-java maven . . snapshots/Class#``().(banana) -// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.get(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#getBanana(). // kind Method // display_name banana // signature_documentation // > private get(): Int -// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana(). // kind Method // display_name banana // signature_documentation // > private set(value: Int): Unit -// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set().(value) +// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana().(value) // kind Parameter // display_name value // signature_documentation @@ -56,9 +56,9 @@ // ^^^^^^ 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#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#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#``().(apple) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``(). Throwable(banana.toString() + apple) { @@ -73,14 +73,14 @@ } // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getAsdf(). 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#asdf.get(). +// ^^^^ definition scip-java maven . . snapshots/Class#getAsdf(). // kind Method // display_name asdf // signature_documentation @@ -109,7 +109,7 @@ // ⌃ enclosing_range_end local 2 } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getAsdf(). // ⌃ enclosing_range_end local 0 // ⌃ enclosing_range_end local 1 @@ -153,12 +153,12 @@ 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#banana.get(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). banana = 42 // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. -// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.get(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). } // ⌃ 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 db2d168b9..544f5b34d 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 @@ -16,17 +16,23 @@ // > public constructor(): CompanionOwner // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion# // ⌄ 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# // 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 +// ^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#``(). +// kind Constructor +// display_name Companion +// signature_documentation +// > private constructor(): CompanionOwner.Companion +// ^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion. +// kind StaticField +// display_name Companion +// signature_documentation +// > public final static val Companion: CompanionOwner.Companion // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#create(). fun create(): CompanionOwner = CompanionOwner() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#create(). @@ -40,6 +46,7 @@ } // ⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#Companion# // ⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#Companion#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#Companion. // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#create(). fun create(): Int = CompanionOwner.create().hashCode() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#create(). @@ -56,3 +63,124 @@ //⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner# //⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Foo# +//⌄ enclosing_range_start scip-java maven . . snapshots/Foo#``(). + class Foo { +// ^^^ definition scip-java maven . . snapshots/Foo# +// kind Class +// display_name Foo +// signature_documentation +// > public final class Foo : Any +// ^^^ definition scip-java maven . . snapshots/Foo#``(). +// kind Constructor +// display_name Foo +// signature_documentation +// > public constructor(): Foo +// ⌄ enclosing_range_start scip-java maven . . snapshots/Foo#Factory# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Foo#Factory#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Foo#Factory. + companion object Factory { +// ^^^^^^^ definition scip-java maven . . snapshots/Foo#Factory# +// kind Class +// display_name Factory +// signature_documentation +// > public final companion object Factory : Any +// ^^^^^^^ definition scip-java maven . . snapshots/Foo#Factory#``(). +// kind Constructor +// display_name Factory +// signature_documentation +// > private constructor(): Foo.Factory +// ^^^^^^^ definition scip-java maven . . snapshots/Foo#Factory. +// kind StaticField +// display_name Factory +// signature_documentation +// > public final static val Factory: Foo.Factory +// ⌄ enclosing_range_start scip-java maven . . snapshots/Foo#Factory#create(). + fun create(): Foo = Foo() +// ^^^^^^ definition scip-java maven . . snapshots/Foo#Factory#create(). +// kind Method +// display_name create +// signature_documentation +// > public final fun create(): Foo +// ^^^ reference scip-java maven . . snapshots/Foo# +// ^^^ reference scip-java maven . . snapshots/Foo#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Foo#Factory#create(). + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/Foo#Factory# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Foo#Factory#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Foo#Factory. + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Foo# +//⌃ enclosing_range_end scip-java maven . . snapshots/Foo#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/useNamedCompanion(). + fun useNamedCompanion() = Foo.Factory.create() +// ^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/useNamedCompanion(). +// kind Method +// display_name useNamedCompanion +// signature_documentation +// > public final fun useNamedCompanion(): Foo +// ^^^ reference scip-java maven . . snapshots/Foo# +// ^^^^^^^ reference scip-java maven . . snapshots/Foo#Factory# +// ^^^^^^ reference scip-java maven . . snapshots/Foo#Factory#create(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useNamedCompanion(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Bar# +//⌄ enclosing_range_start scip-java maven . . snapshots/Bar#``(). + class Bar { +// ^^^ definition scip-java maven . . snapshots/Bar# +// kind Class +// display_name Bar +// signature_documentation +// > public final class Bar : Any +// ^^^ definition scip-java maven . . snapshots/Bar#``(). +// kind Constructor +// display_name Bar +// signature_documentation +// > public constructor(): Bar +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bar#Companion# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bar#Companion#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bar#Companion. + companion object { +// ^^^^^^^^^ definition scip-java maven . . snapshots/Bar#Companion# +// kind Class +// display_name Companion +// signature_documentation +// > public final companion object Companion : Any +// ^^^^^^^^^ definition scip-java maven . . snapshots/Bar#Companion#``(). +// kind Constructor +// display_name Companion +// signature_documentation +// > private constructor(): Bar.Companion +// ^^^^^^^^^ definition scip-java maven . . snapshots/Bar#Companion. +// kind StaticField +// display_name Companion +// signature_documentation +// > public final static val Companion: Bar.Companion +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bar#Companion#instance(). + fun instance(): Bar = Bar() +// ^^^^^^^^ definition scip-java maven . . snapshots/Bar#Companion#instance(). +// kind Method +// display_name instance +// signature_documentation +// > public final fun instance(): Bar +// ^^^ reference scip-java maven . . snapshots/Bar# +// ^^^ reference scip-java maven . . snapshots/Bar#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bar#Companion#instance(). + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bar#Companion# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bar#Companion#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bar#Companion. + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Bar# +//⌃ enclosing_range_end scip-java maven . . snapshots/Bar#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/useUnnamedCompanion(). + fun useUnnamedCompanion() = Bar.instance() +// ^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/useUnnamedCompanion(). +// kind Method +// display_name useUnnamedCompanion +// signature_documentation +// > public final fun useUnnamedCompanion(): Bar +// ^^^ reference scip-java maven . . snapshots/Bar# +// ^^^^^^^^ reference scip-java maven . . snapshots/Bar#Companion#instance(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useUnnamedCompanion(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt new file mode 100644 index 000000000..3b838cb70 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Constructors.kt @@ -0,0 +1,110 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#``().(s) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#s. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#getS(). + class Overloaded(val s: String) { +// ^^^^^^^^^^ definition scip-java maven . . snapshots/Overloaded# +// kind Class +// display_name Overloaded +// signature_documentation +// > public final class Overloaded : Any +// ^^^^^^^^^^ definition scip-java maven . . snapshots/Overloaded#``(). +// kind Constructor +// display_name Overloaded +// signature_documentation +// > public constructor(s: String): Overloaded +// ^ definition scip-java maven . . snapshots/Overloaded#``().(s) +// kind Parameter +// display_name s +// signature_documentation +// > s: String +// ^ definition scip-java maven . . snapshots/Overloaded#s. +// kind Property +// display_name s +// signature_documentation +// > public final val s: String +// ^ reference scip-java maven . . snapshots/Overloaded#``().(s) +// ^ definition scip-java maven . . snapshots/Overloaded#getS(). +// kind Method +// display_name s +// signature_documentation +// > public get(): String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#``().(s) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#s. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#getS(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#``(+1). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Overloaded#``(+1).(i) + constructor(i: Int) : this(i.toString()) +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Overloaded#``(+1). +// kind Constructor +// display_name Overloaded +// signature_documentation +// > public constructor(i: Int): Overloaded +// ^ definition scip-java maven . . snapshots/Overloaded#``(+1).(i) +// kind Parameter +// display_name i +// signature_documentation +// > i: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ reference scip-java maven . . snapshots/Overloaded#``(+1).(i) +// ^^^^^^^^ reference scip-java maven . . kotlin/Int#toString(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#``(+1).(i) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded#``(+1). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Overloaded# + +//⌄ enclosing_range_start scip-java maven . . snapshots/OverloadedAlias# + typealias OverloadedAlias = Overloaded +// ^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/OverloadedAlias# +// kind TypeAlias +// display_name OverloadedAlias +// signature_documentation +// > public final typealias OverloadedAlias = Overloaded +// > +// ^^^^^^^^^^ reference scip-java maven . . snapshots/Overloaded# +// ⌃ enclosing_range_end scip-java maven . . snapshots/OverloadedAlias# + +//⌄ enclosing_range_start scip-java maven . . snapshots/StringIntMap# + typealias StringIntMap = HashMap +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/StringIntMap# +// kind TypeAlias +// display_name StringIntMap +// signature_documentation +// > public final typealias StringIntMap = HashMap +// > +// ^^^^^^^ reference scip-java maven jdk 17 java/util/HashMap# +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/StringIntMap# + +//⌄ enclosing_range_start scip-java maven . . snapshots/constructors(). + // `Regex` and `OverloadedAlias` reference the same symbols as ConstructorsConsumer.java. + fun constructors() { +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/constructors(). +// kind Method +// display_name constructors +// signature_documentation +// > public final fun constructors(): Unit + // Library constructors are numbered like javac, private ones included. + java.io.File("a", "b") +// ^^^^ reference scip-java maven . . java/ +// ^^ reference scip-java maven . . java/io/ +// ^^^^ reference scip-java maven jdk 17 java/io/File#``(+3). + Regex("a") +// ^^^^^ reference scip-java maven . . kotlin/text/Regex#``(+1). + // Calls through type aliases reference the aliased class's constructor. + StringBuilder("x") +// ^^^^^^^^^^^^^ reference scip-java maven jdk 17 java/lang/StringBuilder#``(+2). + StringIntMap() +// ^^^^^^^^^^^^ reference scip-java maven jdk 17 java/util/HashMap#``(+2). + OverloadedAlias(1) +// ^^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/Overloaded#``(+1). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/constructors(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt new file mode 100644 index 000000000..d0044ec15 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Destructuring.kt @@ -0,0 +1,235 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/Point# +//⌄ enclosing_range_start scip-java maven . . snapshots/Point#equals(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Point#equals().(other) +//⌄ enclosing_range_start scip-java maven . . snapshots/Point#hashCode(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Point#toString(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#copy(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#``().(x) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#x. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#getX(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#component1(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#copy().(x) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#``().(y) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#y. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#getY(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#component2(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Point#copy().(y) + data class Point(val x: Int, val y: Int) +// ^^^^^ definition scip-java maven . . snapshots/Point# +// kind Class +// display_name Point +// signature_documentation +// > public final data class Point : Any +// ^^^^^ definition scip-java maven . . snapshots/Point#``(). +// kind Constructor +// display_name Point +// signature_documentation +// > public constructor(x: Int, y: Int): Point +// ^^^^^ definition scip-java maven . . snapshots/Point#equals(). +// kind Method +// display_name equals +// signature_documentation +// > public open override operator fun equals(other: Any?): Boolean +// relationship scip-java maven . . kotlin/Any#equals(). implementation +// ^^^^^ definition scip-java maven . . snapshots/Point#equals().(other) +// kind Parameter +// display_name other +// signature_documentation +// > other: Any? +// ^^^^^ definition scip-java maven . . snapshots/Point#hashCode(). +// kind Method +// display_name hashCode +// signature_documentation +// > public open override fun hashCode(): Int +// relationship scip-java maven . . kotlin/Any#hashCode(). implementation +// ^^^^^ definition scip-java maven . . snapshots/Point#toString(). +// kind Method +// display_name toString +// signature_documentation +// > public open override fun toString(): String +// relationship scip-java maven . . kotlin/Any#toString(). implementation +// ^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Point#copy(). +// kind Method +// display_name copy +// signature_documentation +// > public final fun copy(x: Int = ..., y: Int = ...): Point +// > +// ^ definition scip-java maven . . snapshots/Point#``().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Int +// ^ definition scip-java maven . . snapshots/Point#x. +// kind Property +// display_name x +// signature_documentation +// > public final val x: Int +// ^ reference scip-java maven . . snapshots/Point#``().(x) +// ^ definition scip-java maven . . snapshots/Point#getX(). +// kind Method +// display_name x +// signature_documentation +// > public get(): Int +// ^ definition scip-java maven . . snapshots/Point#component1(). +// kind Method +// display_name component1 +// signature_documentation +// > public final operator fun component1(): Int +// > +// ^ definition scip-java maven . . snapshots/Point#copy().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Int = ... +// ^ reference scip-java maven . . snapshots/Point#x. +// ^ reference scip-java maven . . snapshots/Point#getX(). +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ definition scip-java maven . . snapshots/Point#``().(y) +// kind Parameter +// display_name y +// signature_documentation +// > y: Int +// ^ definition scip-java maven . . snapshots/Point#y. +// kind Property +// display_name y +// signature_documentation +// > public final val y: Int +// ^ reference scip-java maven . . snapshots/Point#``().(y) +// ^ definition scip-java maven . . snapshots/Point#getY(). +// kind Method +// display_name y +// signature_documentation +// > public get(): Int +// ^ definition scip-java maven . . snapshots/Point#component2(). +// kind Method +// display_name component2 +// signature_documentation +// > public final operator fun component2(): Int +// > +// ^ definition scip-java maven . . snapshots/Point#copy().(y) +// kind Parameter +// display_name y +// signature_documentation +// > y: Int = ... +// ^ reference scip-java maven . . snapshots/Point#y. +// ^ reference scip-java maven . . snapshots/Point#getY(). +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#``().(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#x. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#getX(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#component1(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#copy().(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#``().(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#y. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#getY(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#component2(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#copy().(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#equals(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#equals().(other) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#hashCode(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#toString(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Point#copy(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/destructure(). + fun destructure(): Int { +// ^^^^^^^^^^^ definition scip-java maven . . snapshots/destructure(). +// kind Method +// display_name destructure +// signature_documentation +// > public final fun destructure(): Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌄ enclosing_range_start local 0 +// ⌄ enclosing_range_start local 1 +// ⌄ enclosing_range_start local 2 + val (x, y) = Point(1, 2) +// ^^^^^^^^^^^^^^^^^^^^^^^^ definition local 0 +// kind Property +// display_name +// signature_documentation +// > local val : Point +// ^ definition local 1 +// kind Property +// display_name x +// signature_documentation +// > local val x: Int +// ^ reference scip-java maven . . snapshots/Point#component1(). +// ^ reference local 0 +// ^ definition local 2 +// kind Property +// display_name y +// signature_documentation +// > local val y: Int +// ^ reference scip-java maven . . snapshots/Point#component2(). +// ^ reference local 0 +// ^^^^^ reference scip-java maven . . snapshots/Point#``(). +// ⌃ enclosing_range_end local 1 +// ⌃ enclosing_range_end local 2 +// ⌃ enclosing_range_end local 0 +// ⌄ enclosing_range_start local 3 + val labeled = listOf(Point(3, 4) to "label") +// ^^^^^^^ definition local 3 +// kind Property +// display_name labeled +// signature_documentation +// > local val labeled: List> +// ^^^^^^ reference scip-java maven . . kotlin/collections/listOf(). +// ^^^^^ reference scip-java maven . . snapshots/Point#``(). +// ^^ reference scip-java maven . . kotlin/to(). +// ⌃ enclosing_range_end local 3 +// ⌄ enclosing_range_start local 4 +// ⌄ enclosing_range_start local 5 +// ⌄ enclosing_range_start local 6 +// ⌄ enclosing_range_start local 7 + val total = labeled.sumOf { (point, label) -> point.x + label.length } +// ^^^^^ definition local 4 +// kind Property +// display_name total +// signature_documentation +// > local val total: Int +// ^^^^^^^ reference local 3 +// ^^^^^ reference scip-java maven . . kotlin/collections/Iterable#sumOf(+3). +// ^^^^^^^^^^^^^^ definition local 5 +// kind Parameter +// display_name +// signature_documentation +// > : Pair +// ^^^^^ definition local 6 +// kind Property +// display_name point +// signature_documentation +// > local val point: Point +// ^^^^^ reference scip-java maven . . kotlin/Pair#component1(). +// ^^^^^ reference local 5 +// ^^^^^ definition local 7 +// kind Property +// display_name label +// signature_documentation +// > local val label: String +// ^^^^^ reference scip-java maven . . kotlin/Pair#component2(). +// ^^^^^ reference local 5 +// ^^^^^ reference local 6 +// ^ reference scip-java maven . . snapshots/Point#x. +// ^ reference scip-java maven . . snapshots/Point#getX(). +// ^ reference scip-java maven . . kotlin/Int#plus(+2). +// ^^^^^ reference local 7 +// ^^^^^^ reference scip-java maven . . kotlin/String#length. +// ^^^^^^ reference scip-java maven . . kotlin/String#getLength(). +// ⌃ enclosing_range_end local 6 +// ⌃ enclosing_range_end local 7 +// ⌃ enclosing_range_end local 5 +// ⌃ enclosing_range_end local 4 + return x + y + total +// ^ reference local 1 +// ^ reference scip-java maven . . kotlin/Int#plus(+2). +// ^ reference local 2 +// ^ reference scip-java maven . . kotlin/Int#plus(+2). +// ^^^^^ reference local 4 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/destructure(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt new file mode 100644 index 000000000..2d0133f71 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Enums.kt @@ -0,0 +1,416 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit# +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit#values(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit#valueOf(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit#valueOf().(value) +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit#entries. +//⌄ enclosing_range_start scip-java maven . . snapshots/Suit#getEntries(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#``().(symbol) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#symbol. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#getSymbol(). + enum class Suit(val symbol: Char) { +// ^^^^ definition scip-java maven . . snapshots/Suit# +// kind Class +// display_name Suit +// signature_documentation +// > public final enum class Suit : Enum +// relationship scip-java maven . . kotlin/Enum# implementation +// ^^^^ definition scip-java maven . . snapshots/Suit#``(). +// kind Constructor +// display_name Suit +// signature_documentation +// > private constructor(symbol: Char): Suit +// ^^^^ definition scip-java maven . . snapshots/Suit#values(). +// kind Method +// display_name values +// signature_documentation +// > public final static fun values(): Array +// ^^^^ definition scip-java maven . . snapshots/Suit#valueOf(). +// kind Method +// display_name valueOf +// signature_documentation +// > public final static fun valueOf(value: String): Suit +// ^^^^ definition scip-java maven . . snapshots/Suit#valueOf().(value) +// kind Parameter +// display_name value +// signature_documentation +// > value: String +// ^^^^ definition scip-java maven . . snapshots/Suit#entries. +// kind Property +// display_name entries +// signature_documentation +// > public final static val entries: EnumEntries +// ^^^^ definition scip-java maven . . snapshots/Suit#getEntries(). +// kind Method +// display_name entries +// signature_documentation +// > public static get(): EnumEntries +// ^^^^^^ definition scip-java maven . . snapshots/Suit#``().(symbol) +// kind Parameter +// display_name symbol +// signature_documentation +// > symbol: Char +// ^^^^^^ definition scip-java maven . . snapshots/Suit#symbol. +// kind Property +// display_name symbol +// signature_documentation +// > public final val symbol: Char +// ^^^^^^ reference scip-java maven . . snapshots/Suit#``().(symbol) +// ^^^^^^ definition scip-java maven . . snapshots/Suit#getSymbol(). +// kind Method +// display_name symbol +// signature_documentation +// > public get(): Char +// ^^^^ reference scip-java maven . . kotlin/Char# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#``().(symbol) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#symbol. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#getSymbol(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#HEARTS. + HEARTS('h'), +// ^^^^^^ definition scip-java maven . . snapshots/Suit#HEARTS. +// kind EnumMember +// display_name HEARTS +// signature_documentation +// > public final static enum entry HEARTS: Suit +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#HEARTS. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#SPADES. + SPADES('s'); +// ^^^^^^ definition scip-java maven . . snapshots/Suit#SPADES. +// kind EnumMember +// display_name SPADES +// signature_documentation +// > public final static enum entry SPADES: Suit +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#SPADES. + +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#isRed(). + fun isRed(): Boolean = symbol == 'h' +// ^^^^^ definition scip-java maven . . snapshots/Suit#isRed(). +// kind Method +// display_name isRed +// signature_documentation +// > public final fun isRed(): Boolean +// ^^^^^^^ reference scip-java maven . . kotlin/Boolean# +// ^^^^^^ reference scip-java maven . . snapshots/Suit#symbol. +// ^^^^^^ reference scip-java maven . . snapshots/Suit#getSymbol(). +// ^^ reference scip-java maven . . kotlin/Char#equals(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#isRed(). + +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#Companion# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#Companion#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#Companion. + companion object { +// ^^^^^^^^^ definition scip-java maven . . snapshots/Suit#Companion# +// kind Class +// display_name Companion +// signature_documentation +// > public final companion object Companion : Any +// ^^^^^^^^^ definition scip-java maven . . snapshots/Suit#Companion#``(). +// kind Constructor +// display_name Companion +// signature_documentation +// > private constructor(): Suit.Companion +// ^^^^^^^^^ definition scip-java maven . . snapshots/Suit#Companion. +// kind StaticField +// display_name Companion +// signature_documentation +// > public final static val Companion: Suit.Companion +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#Companion#fromSymbol(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Suit#Companion#fromSymbol().(symbol) +// ⌄ enclosing_range_start local 0 + fun fromSymbol(symbol: Char): Suit? = entries.find { it.symbol == symbol } +// ^^^^^^^^^^ definition scip-java maven . . snapshots/Suit#Companion#fromSymbol(). +// kind Method +// display_name fromSymbol +// signature_documentation +// > public final fun fromSymbol(symbol: Char): Suit? +// ^^^^^^ definition scip-java maven . . snapshots/Suit#Companion#fromSymbol().(symbol) +// kind Parameter +// display_name symbol +// signature_documentation +// > symbol: Char +// ^^^^ reference scip-java maven . . kotlin/Char# +// ^^^^ reference scip-java maven . . snapshots/Suit# +// ^^^^^^^ reference scip-java maven . . snapshots/Suit#entries. +// ^^^^^^^ reference scip-java maven . . snapshots/Suit#getEntries(). +// ^^^^ reference scip-java maven . . kotlin/collections/Iterable#find(). +// ^^^^^^^^^^^^^^^^^^^^^^^ definition local 0 +// kind Parameter +// display_name it +// signature_documentation +// > it: Suit +// ^^ reference local 0 +// ^^^^^^ reference scip-java maven . . snapshots/Suit#symbol. +// ^^^^^^ reference scip-java maven . . snapshots/Suit#getSymbol(). +// ^^ reference scip-java maven . . kotlin/Char#equals(). +// ^^^^^^ reference scip-java maven . . snapshots/Suit#Companion#fromSymbol().(symbol) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#Companion#fromSymbol().(symbol) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#Companion#fromSymbol(). +// ⌃ enclosing_range_end local 0 + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#Companion# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#Companion#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Suit#Companion. + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit# +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit#values(). +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit#valueOf(). +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit#valueOf().(value) +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit#entries. +//⌃ enclosing_range_end scip-java maven . . snapshots/Suit#getEntries(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/describe(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/describe().(suit) + fun describe(suit: Suit): String = +// ^^^^^^^^ definition scip-java maven . . snapshots/describe(). +// kind Method +// display_name describe +// signature_documentation +// > public final fun describe(suit: Suit): String +// ^^^^ definition scip-java maven . . snapshots/describe().(suit) +// kind Parameter +// display_name suit +// signature_documentation +// > suit: Suit +// ^^^^ reference scip-java maven . . snapshots/Suit# +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/describe().(suit) +// ⌄ enclosing_range_start local 1 + when (suit) { +// ^^^^ definition local 1 +// kind Property +// display_name +// signature_documentation +// > local val : Suit +// ^^^^ reference scip-java maven . . snapshots/describe().(suit) +// ⌃ enclosing_range_end local 1 + Suit.HEARTS -> "red" +// ^^^^ reference scip-java maven . . snapshots/Suit# +// ^^^^^^ reference scip-java maven . . snapshots/Suit#HEARTS. + Suit.SPADES -> "black" +// ^^^^ reference scip-java maven . . snapshots/Suit# +// ^^^^^^ reference scip-java maven . . snapshots/Suit#SPADES. + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/describe(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Color# +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#values(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#valueOf(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#valueOf().(value) +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#entries. +//⌄ enclosing_range_start scip-java maven . . snapshots/Color#getEntries(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Color#RED. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Color#GREEN. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Color#BLUE. + enum class Color { RED, GREEN, BLUE } +// ^^^^^ definition scip-java maven . . snapshots/Color# +// kind Class +// display_name Color +// signature_documentation +// > public final enum class Color : Enum +// relationship scip-java maven . . kotlin/Enum# implementation +// ^^^^^ definition scip-java maven . . snapshots/Color#``(). +// kind Constructor +// display_name Color +// signature_documentation +// > private constructor(): Color +// ^^^^^ definition scip-java maven . . snapshots/Color#values(). +// kind Method +// display_name values +// signature_documentation +// > public final static fun values(): Array +// ^^^^^ definition scip-java maven . . snapshots/Color#valueOf(). +// kind Method +// display_name valueOf +// signature_documentation +// > public final static fun valueOf(value: String): Color +// ^^^^^ definition scip-java maven . . snapshots/Color#valueOf().(value) +// kind Parameter +// display_name value +// signature_documentation +// > value: String +// ^^^^^ definition scip-java maven . . snapshots/Color#entries. +// kind Property +// display_name entries +// signature_documentation +// > public final static val entries: EnumEntries +// ^^^^^ definition scip-java maven . . snapshots/Color#getEntries(). +// kind Method +// display_name entries +// signature_documentation +// > public static get(): EnumEntries +// ^^^ definition scip-java maven . . snapshots/Color#RED. +// kind EnumMember +// display_name RED +// signature_documentation +// > public final static enum entry RED: Color +// ^^^^^ definition scip-java maven . . snapshots/Color#GREEN. +// kind EnumMember +// display_name GREEN +// signature_documentation +// > public final static enum entry GREEN: Color +// ^^^^ definition scip-java maven . . snapshots/Color#BLUE. +// kind EnumMember +// display_name BLUE +// signature_documentation +// > public final static enum entry BLUE: Color +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#RED. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#GREEN. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#BLUE. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#values(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#valueOf(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#valueOf().(value) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#entries. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Color#getEntries(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/useEnum(). + fun useEnum(): Color = Color.RED +// ^^^^^^^ definition scip-java maven . . snapshots/useEnum(). +// kind Method +// display_name useEnum +// signature_documentation +// > public final fun useEnum(): Color +// ^^^^^ reference scip-java maven . . snapshots/Color# +// ^^^^^ reference scip-java maven . . snapshots/Color# +// ^^^ reference scip-java maven . . snapshots/Color#RED. +// ⌃ enclosing_range_end scip-java maven . . snapshots/useEnum(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Op# +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#values(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#valueOf(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#valueOf().(value) +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#entries. +//⌄ enclosing_range_start scip-java maven . . snapshots/Op#getEntries(). + enum class Op { +// ^^ definition scip-java maven . . snapshots/Op# +// kind Class +// display_name Op +// signature_documentation +// > public final enum class Op : Enum +// relationship scip-java maven . . kotlin/Enum# implementation +// ^^ definition scip-java maven . . snapshots/Op#``(). +// kind Constructor +// display_name Op +// signature_documentation +// > private constructor(): Op +// ^^ definition scip-java maven . . snapshots/Op#values(). +// kind Method +// display_name values +// signature_documentation +// > public final static fun values(): Array +// ^^ definition scip-java maven . . snapshots/Op#valueOf(). +// kind Method +// display_name valueOf +// signature_documentation +// > public final static fun valueOf(value: String): Op +// ^^ definition scip-java maven . . snapshots/Op#valueOf().(value) +// kind Parameter +// display_name value +// signature_documentation +// > value: String +// ^^ definition scip-java maven . . snapshots/Op#entries. +// kind Property +// display_name entries +// signature_documentation +// > public final static val entries: EnumEntries +// ^^ definition scip-java maven . . snapshots/Op#getEntries(). +// kind Method +// display_name entries +// signature_documentation +// > public static get(): EnumEntries +// ⌄ enclosing_range_start scip-java maven . . snapshots/Op#PLUS. +// ⌄ enclosing_range_start local 2 +// ⌄ enclosing_range_start local 3 + // The entry body is an anonymous subclass; its override has a local symbol. + PLUS { +// ^^^^ definition scip-java maven . . snapshots/Op#PLUS. +// kind EnumMember +// display_name PLUS +// signature_documentation +// > public final static enum entry PLUS: Op +// ^^^^ definition local 2 +// kind Class +// display_name +// signature_documentation +// > object : Op +// relationship scip-java maven . . snapshots/Op# implementation +// ^^^^ definition local 3 +// kind Constructor +// display_name PLUS +// signature_documentation +// > private constructor(): +// ⌄ enclosing_range_start local 4 +// ⌄ enclosing_range_start local 5 +// ⌄ enclosing_range_start local 6 + override fun apply(a: Int, b: Int) = a + b +// ^^^^^ definition local 4 +// kind Method +// display_name apply +// signature_documentation +// > public open override fun apply(a: Int, b: Int): Int +// relationship scip-java maven . . snapshots/Op#apply(). implementation +// ^ definition local 5 +// kind Parameter +// display_name a +// signature_documentation +// > a: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ definition local 6 +// kind Parameter +// display_name b +// signature_documentation +// > b: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ reference local 5 +// ^ reference scip-java maven . . kotlin/Int#plus(+2). +// ^ reference local 6 +// ⌃ enclosing_range_end local 5 +// ⌃ enclosing_range_end local 6 +// ⌃ enclosing_range_end local 4 + }; +// ⌃ enclosing_range_end scip-java maven . . snapshots/Op#PLUS. +// ⌃ enclosing_range_end local 2 +// ⌃ enclosing_range_end local 3 + +// ⌄ enclosing_range_start scip-java maven . . snapshots/Op#apply(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Op#apply().(a) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Op#apply().(b) + abstract fun apply(a: Int, b: Int): Int +// ^^^^^ definition scip-java maven . . snapshots/Op#apply(). +// kind Method +// display_name apply +// signature_documentation +// > public abstract fun apply(a: Int, b: Int): Int +// > +// ^ definition scip-java maven . . snapshots/Op#apply().(a) +// kind Parameter +// display_name a +// signature_documentation +// > a: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ definition scip-java maven . . snapshots/Op#apply().(b) +// kind Parameter +// display_name b +// signature_documentation +// > b: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Op#apply().(a) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Op#apply().(b) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Op#apply(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Op# +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#``(). +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#values(). +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#valueOf(). +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#valueOf().(value) +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#entries. +//⌃ enclosing_range_end scip-java maven . . snapshots/Op#getEntries(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt new file mode 100644 index 000000000..e6cfca31e --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Extensions.kt @@ -0,0 +1,105 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/String#foo(). + // Cross-package extensions belong to snapshots, not to the receiver's kotlin package. + fun String.foo(): Int = 42 +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^ definition scip-java maven . . snapshots/String#foo(). +// kind Method +// display_name foo +// signature_documentation +// > public final fun String.foo(): Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/String#foo(). +//⌄ enclosing_range_start scip-java maven . . snapshots/useExtension(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/useExtension().(s) + fun useExtension(s: String) = s.foo() +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/useExtension(). +// kind Method +// display_name useExtension +// signature_documentation +// > public final fun useExtension(s: String): Int +// ^ definition scip-java maven . . snapshots/useExtension().(s) +// kind Parameter +// display_name s +// signature_documentation +// > s: String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^ reference scip-java maven . . snapshots/useExtension().(s) +// ^^^ reference scip-java maven . . snapshots/String#foo(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useExtension().(s) +// ⌃ enclosing_range_end scip-java maven . . snapshots/useExtension(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Int#asString. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Int#getAsString(). + val Int.asString: String get() = this.toString() +// ^^^ reference scip-java maven . . kotlin/Int# +// ^^^^^^^^ definition scip-java maven . . snapshots/Int#asString. +// kind Property +// display_name asString +// signature_documentation +// > public final val Int.asString: String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^ definition scip-java maven . . snapshots/Int#getAsString(). +// kind Method +// display_name asString +// signature_documentation +// > public get(): String +// ^^^^^^^^ reference scip-java maven . . kotlin/Int#toString(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Int#asString. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Int#getAsString(). +//⌄ enclosing_range_start scip-java maven . . snapshots/useExtensionProperty(). + fun useExtensionProperty() = 42.asString +// ^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/useExtensionProperty(). +// kind Method +// display_name useExtensionProperty +// signature_documentation +// > public final fun useExtensionProperty(): String +// ^^^^^^^^ reference scip-java maven . . snapshots/Int#asString. +// ^^^^^^^^ reference scip-java maven . . snapshots/Int#getAsString(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useExtensionProperty(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/ExtensionReceiver# +//⌄ enclosing_range_start scip-java maven . . snapshots/ExtensionReceiver#``(). + class ExtensionReceiver { +// ^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/ExtensionReceiver# +// kind Class +// display_name ExtensionReceiver +// signature_documentation +// > public final class ExtensionReceiver : Any +// ^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/ExtensionReceiver#``(). +// kind Constructor +// display_name ExtensionReceiver +// signature_documentation +// > public constructor(): ExtensionReceiver +// ⌄ enclosing_range_start scip-java maven . . snapshots/ExtensionReceiver#foo(). + fun foo() {} +// ^^^ definition scip-java maven . . snapshots/ExtensionReceiver#foo(). +// kind Method +// display_name foo +// signature_documentation +// > public final fun foo(): Unit +// ⌃ enclosing_range_end scip-java maven . . snapshots/ExtensionReceiver#foo(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/ExtensionReceiver# +//⌃ enclosing_range_end scip-java maven . . snapshots/ExtensionReceiver#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/ExtensionReceiver#foo(+1). + // The extension is numbered after the receiver's member, so it gets foo(+1). +// ⌄ enclosing_range_start scip-java maven . . snapshots/ExtensionReceiver#foo(+1).(x) + fun ExtensionReceiver.foo(x: Int) {} +// ^^^^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/ExtensionReceiver# +// ^^^ definition scip-java maven . . snapshots/ExtensionReceiver#foo(+1). +// kind Method +// display_name foo +// signature_documentation +// > public final fun ExtensionReceiver.foo(x: Int): Unit +// ^ definition scip-java maven . . snapshots/ExtensionReceiver#foo(+1).(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/ExtensionReceiver#foo(+1).(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/ExtensionReceiver#foo(+1). + 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 0ee9ed764..cf5020994 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 @@ -22,3 +22,149 @@ } //⌃ enclosing_range_end scip-java maven . . snapshots/sampleText(). +//⌄ enclosing_range_start scip-java maven . . snapshots/MyClass# +//⌄ enclosing_range_start scip-java maven . . snapshots/MyClass#``(). + class MyClass +// ^^^^^^^ definition scip-java maven . . snapshots/MyClass# +// kind Class +// display_name MyClass +// signature_documentation +// > public final class MyClass : Any +// ^^^^^^^ definition scip-java maven . . snapshots/MyClass#``(). +// kind Constructor +// display_name MyClass +// signature_documentation +// > public constructor(): MyClass +// ⌃ enclosing_range_end scip-java maven . . snapshots/MyClass# +// ⌃ enclosing_range_end scip-java maven . . snapshots/MyClass#``(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/bar(). + fun bar(): MyClass = MyClass() +// ^^^ definition scip-java maven . . snapshots/bar(). +// kind Method +// display_name bar +// signature_documentation +// > public final fun bar(): MyClass +// ^^^^^^^ reference scip-java maven . . snapshots/MyClass# +// ^^^^^^^ reference scip-java maven . . snapshots/MyClass#``(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/bar(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/greet(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/greet().(name) + fun greet(name: String) = "Hello, $name!" +// ^^^^^ definition scip-java maven . . snapshots/greet(). +// kind Method +// display_name greet +// signature_documentation +// > public final fun greet(name: String): String +// ^^^^ definition scip-java maven . . snapshots/greet().(name) +// kind Parameter +// display_name name +// signature_documentation +// > name: String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^^ reference scip-java maven . . snapshots/greet().(name) +// ⌃ enclosing_range_end scip-java maven . . snapshots/greet().(name) +// ⌃ enclosing_range_end scip-java maven . . snapshots/greet(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/add(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/add().(x) +// ⌄ enclosing_range_start scip-java maven . . snapshots/add().(y) + fun add(x: Int, y: Int): Int = x + y +// ^^^ definition scip-java maven . . snapshots/add(). +// kind Method +// display_name add +// signature_documentation +// > public final fun add(x: Int, y: Int): Int +// ^ definition scip-java maven . . snapshots/add().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ definition scip-java maven . . snapshots/add().(y) +// kind Parameter +// display_name y +// signature_documentation +// > y: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ reference scip-java maven . . snapshots/add().(x) +// ^ reference scip-java maven . . kotlin/Int#plus(+2). +// ^ reference scip-java maven . . snapshots/add().(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add().(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add().(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(). +//⌄ enclosing_range_start scip-java maven . . snapshots/add(+1). +// ⌄ enclosing_range_start scip-java maven . . snapshots/add(+1).(x) +// ⌄ enclosing_range_start scip-java maven . . snapshots/add(+1).(y) + fun add(x: Double, y: Double): Double = x + y +// ^^^ definition scip-java maven . . snapshots/add(+1). +// kind Method +// display_name add +// signature_documentation +// > public final fun add(x: Double, y: Double): Double +// ^ definition scip-java maven . . snapshots/add(+1).(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Double +// ^^^^^^ reference scip-java maven . . kotlin/Double# +// ^ definition scip-java maven . . snapshots/add(+1).(y) +// kind Parameter +// display_name y +// signature_documentation +// > y: Double +// ^^^^^^ reference scip-java maven . . kotlin/Double# +// ^^^^^^ reference scip-java maven . . kotlin/Double# +// ^ reference scip-java maven . . snapshots/add(+1).(x) +// ^ reference scip-java maven . . kotlin/Double#plus(+5). +// ^ reference scip-java maven . . snapshots/add(+1).(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+1).(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+1).(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+1). +//⌄ enclosing_range_start scip-java maven . . snapshots/add(+2). +// ⌄ enclosing_range_start scip-java maven . . snapshots/add(+2).(x) +// ⌄ enclosing_range_start scip-java maven . . snapshots/add(+2).(y) + fun add(x: String, y: String): String = x + y +// ^^^ definition scip-java maven . . snapshots/add(+2). +// kind Method +// display_name add +// signature_documentation +// > public final fun add(x: String, y: String): String +// ^ definition scip-java maven . . snapshots/add(+2).(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^ definition scip-java maven . . snapshots/add(+2).(y) +// kind Parameter +// display_name y +// signature_documentation +// > y: String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ^ reference scip-java maven . . snapshots/add(+2).(x) +// ^ reference scip-java maven . . kotlin/String#plus(). +// ^ reference scip-java maven . . snapshots/add(+2).(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+2).(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+2).(y) +// ⌃ enclosing_range_end scip-java maven . . snapshots/add(+2). + +//⌄ enclosing_range_start scip-java maven . . snapshots/useOverloads(). + fun useOverloads() { +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/useOverloads(). +// kind Method +// display_name useOverloads +// signature_documentation +// > public final fun useOverloads(): Unit + add(1, 2) +// ^^^ reference scip-java maven . . snapshots/add(). + add(1.0, 2.0) +// ^^^ reference scip-java maven . . snapshots/add(+1). + add("a", "b") +// ^^^ reference scip-java maven . . snapshots/add(+2). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/useOverloads(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt new file mode 100644 index 000000000..b0f77af39 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Generics.kt @@ -0,0 +1,290 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/Container# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#[T] +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#``().(items) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#items. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#getItems(). + class Container>(private val items: MutableList) { +// ^^^^^^^^^ definition scip-java maven . . snapshots/Container# +// kind Class +// display_name Container +// signature_documentation +// > public final class Container> : Any +// ^^^^^^^^^ definition scip-java maven . . snapshots/Container#``(). +// kind Constructor +// display_name Container +// signature_documentation +// > public constructor>(items: MutableList): Container +// ^ definition scip-java maven . . snapshots/Container#[T] +// kind TypeParameter +// display_name T +// signature_documentation +// > T : Comparable +// ^^^^^^^^^^ reference scip-java maven . . kotlin/Comparable# +// ^ reference scip-java maven . . snapshots/Container#[T] +// ^^^^^ definition scip-java maven . . snapshots/Container#``().(items) +// kind Parameter +// display_name items +// signature_documentation +// > items: MutableList +// ^^^^^ definition scip-java maven . . snapshots/Container#items. +// kind Property +// display_name items +// signature_documentation +// > private final val items: MutableList +// ^^^^^ reference scip-java maven . . snapshots/Container#``().(items) +// ^^^^^ definition scip-java maven . . snapshots/Container#getItems(). +// kind Method +// display_name items +// signature_documentation +// > private get(): MutableList +// ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/MutableList# +// ^ reference scip-java maven . . snapshots/Container#[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#``().(items) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#items. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#getItems(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#add(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#add().(item) + fun add(item: T): Container { +// ^^^ definition scip-java maven . . snapshots/Container#add(). +// kind Method +// display_name add +// signature_documentation +// > public final fun add(item: T): Container +// ^^^^ definition scip-java maven . . snapshots/Container#add().(item) +// kind Parameter +// display_name item +// signature_documentation +// > item: T +// ^ reference scip-java maven . . snapshots/Container#[T] +// ^^^^^^^^^ reference scip-java maven . . snapshots/Container# +// ^ reference scip-java maven . . snapshots/Container#[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#add().(item) + items.add(item) +// ^^^^^ reference scip-java maven . . snapshots/Container#items. +// ^^^^^ reference scip-java maven . . snapshots/Container#getItems(). +// ^^^ reference scip-java maven . . kotlin/collections/MutableList#add(). +// ^^^^ reference scip-java maven . . snapshots/Container#add().(item) + return this + } +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#add(). + +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#mapItems(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#mapItems().[R] +// ⌄ enclosing_range_start scip-java maven . . snapshots/Container#mapItems().(transform) + fun mapItems(transform: (T) -> R?): List = items.mapNotNull(transform) +// ^ definition scip-java maven . . snapshots/Container#mapItems().[R] +// kind TypeParameter +// display_name R +// signature_documentation +// > R : Any +// ^^^ reference scip-java maven . . kotlin/Any# +// ^^^^^^^^ definition scip-java maven . . snapshots/Container#mapItems(). +// kind Method +// display_name mapItems +// signature_documentation +// > public final fun mapItems(transform: (T) -> R?): List +// ^^^^^^^^^ definition scip-java maven . . snapshots/Container#mapItems().(transform) +// kind Parameter +// display_name transform +// signature_documentation +// > transform: (T) -> R? +// ^ reference scip-java maven . . snapshots/Container#[T] +// ^ reference scip-java maven . . snapshots/Container#mapItems().[R] +// ^^^^ reference scip-java maven . . kotlin/collections/List# +// ^ reference scip-java maven . . snapshots/Container#mapItems().[R] +// ^^^^^ reference scip-java maven . . snapshots/Container#items. +// ^^^^^ reference scip-java maven . . snapshots/Container#getItems(). +// ^^^^^^^^^^ reference scip-java maven . . kotlin/collections/Iterable#mapNotNull(). +// ^^^^^^^^^ reference scip-java maven . . snapshots/Container#mapItems().(transform) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#mapItems().[R] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#mapItems().(transform) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Container#mapItems(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Container# + +//⌄ enclosing_range_start scip-java maven . . snapshots/firstOrSelf(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/firstOrSelf().[T] +// ⌄ enclosing_range_start scip-java maven . . snapshots/firstOrSelf().(items) +// ⌄ enclosing_range_start scip-java maven . . snapshots/firstOrSelf().(fallback) + fun firstOrSelf(items: List, fallback: T): T = items.firstOrNull() ?: fallback +// ^ definition scip-java maven . . snapshots/firstOrSelf().[T] +// kind TypeParameter +// display_name T +// signature_documentation +// > T +// ^^^^^^^^^^^ definition scip-java maven . . snapshots/firstOrSelf(). +// kind Method +// display_name firstOrSelf +// signature_documentation +// > public final fun firstOrSelf(items: List, fallback: T): T +// ^^^^^ definition scip-java maven . . snapshots/firstOrSelf().(items) +// kind Parameter +// display_name items +// signature_documentation +// > items: List +// ^^^^ reference scip-java maven . . kotlin/collections/List# +// ^ reference scip-java maven . . snapshots/firstOrSelf().[T] +// ^^^^^^^^ definition scip-java maven . . snapshots/firstOrSelf().(fallback) +// kind Parameter +// display_name fallback +// signature_documentation +// > fallback: T +// ^ reference scip-java maven . . snapshots/firstOrSelf().[T] +// ^ reference scip-java maven . . snapshots/firstOrSelf().[T] +// ^^^^^ reference scip-java maven . . snapshots/firstOrSelf().(items) +// ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/List#firstOrNull(). +// ^^^^^^^^ reference scip-java maven . . snapshots/firstOrSelf().(fallback) +// ⌃ enclosing_range_end scip-java maven . . snapshots/firstOrSelf().[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/firstOrSelf().(items) +// ⌃ enclosing_range_end scip-java maven . . snapshots/firstOrSelf().(fallback) +// ⌃ enclosing_range_end scip-java maven . . snapshots/firstOrSelf(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/StringContainer# + typealias StringContainer = Container +// ^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/StringContainer# +// kind TypeAlias +// display_name StringContainer +// signature_documentation +// > public final typealias StringContainer = Container +// > +// ^^^^^^^^^ reference scip-java maven . . snapshots/Container# +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/StringContainer# + +//⌄ enclosing_range_start scip-java maven . . snapshots/useContainer(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/useContainer().(container) + fun useContainer(container: StringContainer): StringContainer = container.add("hello") +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/useContainer(). +// kind Method +// display_name useContainer +// signature_documentation +// > public final fun useContainer(container: StringContainer): StringContainer +// ^^^^^^^^^ definition scip-java maven . . snapshots/useContainer().(container) +// kind Parameter +// display_name container +// signature_documentation +// > container: StringContainer +// ^^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/Container# +// ^^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/Container# +// ^^^^^^^^^ reference scip-java maven . . snapshots/useContainer().(container) +// ^^^ reference scip-java maven . . snapshots/Container#add(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useContainer().(container) +// ⌃ enclosing_range_end scip-java maven . . snapshots/useContainer(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/MyAlias# + typealias MyAlias = Int +// ^^^^^^^ definition scip-java maven . . snapshots/MyAlias# +// kind TypeAlias +// display_name MyAlias +// signature_documentation +// > public final typealias MyAlias = Int +// > +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/MyAlias# +//⌄ enclosing_range_start scip-java maven . . snapshots/aliasValue. +//⌄ enclosing_range_start scip-java maven . . snapshots/getAliasValue(). + val aliasValue: MyAlias = 42 +// ^^^^^^^^^^ definition scip-java maven . . snapshots/aliasValue. +// kind Property +// display_name aliasValue +// signature_documentation +// > public final val aliasValue: MyAlias +// ^^^^^^^^^^ definition scip-java maven . . snapshots/getAliasValue(). +// kind Method +// display_name aliasValue +// signature_documentation +// > public get(): MyAlias +// ^^^^^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end scip-java maven . . snapshots/aliasValue. +// ⌃ enclosing_range_end scip-java maven . . snapshots/getAliasValue(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/identity(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/identity().[T] +// ⌄ enclosing_range_start scip-java maven . . snapshots/identity().(x) + fun identity(x: T): T = x +// ^ definition scip-java maven . . snapshots/identity().[T] +// kind TypeParameter +// display_name T +// signature_documentation +// > T +// ^^^^^^^^ definition scip-java maven . . snapshots/identity(). +// kind Method +// display_name identity +// signature_documentation +// > public final fun identity(x: T): T +// ^ definition scip-java maven . . snapshots/identity().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: T +// ^ reference scip-java maven . . snapshots/identity().[T] +// ^ reference scip-java maven . . snapshots/identity().[T] +// ^ reference scip-java maven . . snapshots/identity().(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/identity().[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/identity().(x) +// ⌃ enclosing_range_end scip-java maven . . snapshots/identity(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Box# +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#[T] +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#``().(value) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#value. +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#getValue(). + class Box(val value: T) { +// ^^^ definition scip-java maven . . snapshots/Box# +// kind Class +// display_name Box +// signature_documentation +// > public final class Box : Any +// ^^^ definition scip-java maven . . snapshots/Box#``(). +// kind Constructor +// display_name Box +// signature_documentation +// > public constructor(value: T): Box +// ^ definition scip-java maven . . snapshots/Box#[T] +// kind TypeParameter +// display_name T +// signature_documentation +// > T +// ^^^^^ definition scip-java maven . . snapshots/Box#``().(value) +// kind Parameter +// display_name value +// signature_documentation +// > value: T +// ^^^^^ definition scip-java maven . . snapshots/Box#value. +// kind Property +// display_name value +// signature_documentation +// > public final val value: T +// ^^^^^ reference scip-java maven . . snapshots/Box#``().(value) +// ^^^^^ definition scip-java maven . . snapshots/Box#getValue(). +// kind Method +// display_name value +// signature_documentation +// > public get(): T +// ^ reference scip-java maven . . snapshots/Box#[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#[T] +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#``().(value) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#value. +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#getValue(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#``(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Box#unwrap(). + fun unwrap(): T = value +// ^^^^^^ definition scip-java maven . . snapshots/Box#unwrap(). +// kind Method +// display_name unwrap +// signature_documentation +// > public final fun unwrap(): T +// ^ reference scip-java maven . . snapshots/Box#[T] +// ^^^^^ reference scip-java maven . . snapshots/Box#value. +// ^^^^^ reference scip-java maven . . snapshots/Box#getValue(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Box#unwrap(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Box# + 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 0041a9f5a..5c3989d7a 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 @@ -8,7 +8,7 @@ // kind Class // display_name Overrides // signature_documentation -// > public final class Overrides : {kotlin/AutoCloseable=} AutoCloseable +// > public final class Overrides : AutoCloseable // relationship scip-java maven jdk 17 java/lang/AutoCloseable# implementation // ^^^^^^^^^ definition scip-java maven . . snapshots/Overrides#``(). // kind Constructor @@ -40,21 +40,21 @@ // signature_documentation // > public abstract interface Animal : Any // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#getFavoriteNumber(). 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#favoriteNumber.get(). +// ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#getFavoriteNumber(). // 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#favoriteNumber.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#getFavoriteNumber(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#sound(). fun sound(): String // ^^^^^ definition scip-java maven . . snapshots/Animal#sound(). @@ -90,15 +90,15 @@ // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#favoriteNumber.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#getFavoriteNumber(). get() = 42 -// ^^^ definition scip-java maven . . snapshots/Bird#favoriteNumber.get(). +// ^^^ definition scip-java maven . . snapshots/Bird#getFavoriteNumber(). // 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#favoriteNumber.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#getFavoriteNumber(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#sound(). override fun sound(): String { @@ -138,15 +138,15 @@ // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#favoriteNumber.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#getFavoriteNumber(). get() = 1337 -// ^^^ definition scip-java maven . . snapshots/Seagull#favoriteNumber.get(). +// ^^^ definition scip-java maven . . snapshots/Seagull#getFavoriteNumber(). // 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#favoriteNumber.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#getFavoriteNumber(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Seagull#sound(). @@ -163,3 +163,57 @@ //⌃ enclosing_range_end scip-java maven . . snapshots/Seagull# //⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/Named# + interface Named +// ^^^^^ definition scip-java maven . . snapshots/Named# +// kind Interface +// display_name Named +// signature_documentation +// > public abstract interface Named : Any +// ⌃ enclosing_range_end scip-java maven . . snapshots/Named# +//⌄ enclosing_range_start scip-java maven . . snapshots/Speakable# + interface Speakable +// ^^^^^^^^^ definition scip-java maven . . snapshots/Speakable# +// kind Interface +// display_name Speakable +// signature_documentation +// > public abstract interface Speakable : Any +// ⌃ enclosing_range_end scip-java maven . . snapshots/Speakable# +//⌄ enclosing_range_start scip-java maven . . snapshots/Person# +//⌄ enclosing_range_start scip-java maven . . snapshots/Person#``(). + class Person : Named, Speakable +// ^^^^^^ definition scip-java maven . . snapshots/Person# +// kind Class +// display_name Person +// signature_documentation +// > public final class Person : Named, Speakable +// relationship scip-java maven . . snapshots/Named# implementation +// relationship scip-java maven . . snapshots/Speakable# implementation +// ^^^^^^ definition scip-java maven . . snapshots/Person#``(). +// kind Constructor +// display_name Person +// signature_documentation +// > public constructor(): Person +// ^^^^^ reference scip-java maven . . snapshots/Named# +// ^^^^^^^^^ reference scip-java maven . . snapshots/Speakable# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Person# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Person#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/usePerson(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/usePerson().(p) + fun usePerson(p: Person): Named = p +// ^^^^^^^^^ definition scip-java maven . . snapshots/usePerson(). +// kind Method +// display_name usePerson +// signature_documentation +// > public final fun usePerson(p: Person): Named +// ^ definition scip-java maven . . snapshots/usePerson().(p) +// kind Parameter +// display_name p +// signature_documentation +// > p: Person +// ^^^^^^ reference scip-java maven . . snapshots/Person# +// ^^^^^ reference scip-java maven . . snapshots/Named# +// ^ reference scip-java maven . . snapshots/usePerson().(p) +// ⌃ enclosing_range_end scip-java maven . . snapshots/usePerson().(p) +// ⌃ enclosing_range_end scip-java maven . . snapshots/usePerson(). + 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 1748c0b3b..b5f36c317 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,7 +2,7 @@ // ^^^^^^^^^ reference scip-java maven . . snapshots/ //⌄ enclosing_range_start scip-java maven . . snapshots/x. -//⌄ enclosing_range_start scip-java maven . . snapshots/x.get(). +//⌄ enclosing_range_start scip-java maven . . snapshots/getX(). // ⌄ enclosing_range_start local 0 // ⌄ enclosing_range_start local 1 val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } @@ -11,12 +11,13 @@ // display_name x // signature_documentation // > public final val x: Unit -// ^ definition scip-java maven . . snapshots/x.get(). +// ^ definition scip-java maven . . snapshots/getX(). // kind Method // display_name x // signature_documentation // > public get(): Unit // ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/arrayListOf(). +// ^^^^^^ reference scip-java maven . . kotlin/String# // ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/Iterable#forEachIndexed(). // ^ definition local 0 // kind Parameter @@ -34,17 +35,17 @@ // ⌃ 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/x.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/getX(). //⌄ enclosing_range_start scip-java maven . . snapshots/y. -//⌄ enclosing_range_start scip-java maven . . snapshots/y.get(). +//⌄ enclosing_range_start scip-java maven . . snapshots/getY(). 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/y.get(). +// ^ definition scip-java maven . . snapshots/getY(). // kind Method // display_name y // signature_documentation @@ -52,10 +53,10 @@ // ^^^ reference scip-java maven . . kotlin/run(+1). // ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/String#toByteArray(). // ⌃ enclosing_range_end scip-java maven . . snapshots/y. -// ⌃ enclosing_range_end scip-java maven . . snapshots/y.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/getY(). //⌄ enclosing_range_start scip-java maven . . snapshots/z. -//⌄ enclosing_range_start scip-java maven . . snapshots/z.get(). +//⌄ enclosing_range_start scip-java maven . . snapshots/getZ(). // ⌄ enclosing_range_start local 2 val z = y.let { it.size } // ^ definition scip-java maven . . snapshots/z. @@ -63,13 +64,13 @@ // display_name z // signature_documentation // > public final val z: Int -// ^ definition scip-java maven . . snapshots/z.get(). +// ^ definition scip-java maven . . snapshots/getZ(). // kind Method // display_name z // signature_documentation // > public get(): Int // ^ reference scip-java maven . . snapshots/y. -// ^ reference scip-java maven . . snapshots/y.get(). +// ^ reference scip-java maven . . snapshots/getY(). // ^^^ reference scip-java maven . . kotlin/let(). // ^^^^^^^^^^^ definition local 2 // kind Parameter @@ -78,8 +79,36 @@ // > it: ByteArray // ^^ reference local 2 // ^^^^ reference scip-java maven . . kotlin/ByteArray#size. -// ^^^^ reference scip-java maven . . kotlin/ByteArray#size.get(). +// ^^^^ reference scip-java maven . . kotlin/ByteArray#getSize(). // ⌃ enclosing_range_end scip-java maven . . snapshots/z. -// ⌃ enclosing_range_end scip-java maven . . snapshots/z.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/getZ(). // ⌃ enclosing_range_end local 2 +//⌄ enclosing_range_start scip-java maven . . snapshots/useLambda(). + fun useLambda() { +// ^^^^^^^^^ definition scip-java maven . . snapshots/useLambda(). +// kind Method +// display_name useLambda +// signature_documentation +// > public final fun useLambda(): Unit +// ⌄ enclosing_range_start local 3 +// ⌄ enclosing_range_start local 4 + val f = { n: Int -> n * 2 } +// ^ definition local 3 +// kind Property +// display_name f +// signature_documentation +// > local val f: (Int) -> Int +// ^ definition local 4 +// kind Parameter +// display_name n +// signature_documentation +// > n: Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ^ reference local 4 +// ^ reference scip-java maven . . kotlin/Int#times(+2). +// ⌃ enclosing_range_end local 4 +// ⌃ enclosing_range_end local 3 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/useLambda(). + diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt new file mode 100644 index 000000000..a9bbfb96a --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/LocalDeclarations.kt @@ -0,0 +1,164 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/localClasses(). + fun localClasses() { +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/localClasses(). +// kind Method +// display_name localClasses +// signature_documentation +// > public final fun localClasses(): Unit +// ⌄ enclosing_range_start local 0 +// ⌄ enclosing_range_start local 1 + class LocalClass { +// ^^^^^^^^^^ definition local 0 +// kind Class +// display_name LocalClass +// signature_documentation +// > local final class LocalClass : Any +// ^^^^^^^^^^ definition local 1 +// kind Constructor +// display_name LocalClass +// signature_documentation +// > public constructor(): LocalClass +// ⌄ enclosing_range_start local 2 + fun localClassMethod() {} +// ^^^^^^^^^^^^^^^^ definition local 2 +// kind Method +// display_name localClassMethod +// signature_documentation +// > public final fun localClassMethod(): Unit +// ⌃ enclosing_range_end local 2 + } +// ⌃ enclosing_range_end local 0 +// ⌃ enclosing_range_end local 1 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/localClasses(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Interface# + interface Interface { +// ^^^^^^^^^ definition scip-java maven . . snapshots/Interface# +// kind Interface +// display_name Interface +// signature_documentation +// > public abstract interface Interface : Any +// ⌄ enclosing_range_start scip-java maven . . snapshots/Interface#foo(). + fun foo() +// ^^^ definition scip-java maven . . snapshots/Interface#foo(). +// kind Method +// display_name foo +// signature_documentation +// > public abstract fun foo(): Unit +// > +// ⌃ enclosing_range_end scip-java maven . . snapshots/Interface#foo(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/Interface# + +//⌄ enclosing_range_start scip-java maven . . snapshots/anonymousObjects(). + fun anonymousObjects() { +// ^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/anonymousObjects(). +// kind Method +// display_name anonymousObjects +// signature_documentation +// > public final fun anonymousObjects(): Unit + // Each object and its members need distinct local symbols, with no INSTANCE field. +// ⌄ enclosing_range_start local 3 +// ⌄ enclosing_range_start local 4 +// ⌄ enclosing_range_start local 5 + val a = object : Interface { +// ^ definition local 3 +// kind Property +// display_name a +// signature_documentation +// > local val a: `` +// ^^^^^^ definition local 4 +// kind Class +// display_name +// signature_documentation +// > object : Interface +// relationship scip-java maven . . snapshots/Interface# implementation +// ^^^^^^ definition local 5 +// kind Constructor +// display_name +// signature_documentation +// > private constructor(): +// ^^^^^^^^^ reference scip-java maven . . snapshots/Interface# +// ⌄ enclosing_range_start local 6 + override fun foo() {} +// ^^^ definition local 6 +// kind Method +// display_name foo +// signature_documentation +// > public open override fun foo(): Unit +// relationship scip-java maven . . snapshots/Interface#foo(). implementation +// ⌃ enclosing_range_end local 6 + } +// ⌃ enclosing_range_end local 3 +// ⌃ enclosing_range_end local 4 +// ⌃ enclosing_range_end local 5 +// ⌄ enclosing_range_start local 7 +// ⌄ enclosing_range_start local 8 +// ⌄ enclosing_range_start local 9 + val b = object : Interface { +// ^ definition local 7 +// kind Property +// display_name b +// signature_documentation +// > local val b: `` +// ^^^^^^ definition local 8 +// kind Class +// display_name +// signature_documentation +// > object : Interface +// relationship scip-java maven . . snapshots/Interface# implementation +// ^^^^^^ definition local 9 +// kind Constructor +// display_name +// signature_documentation +// > private constructor(): +// ^^^^^^^^^ reference scip-java maven . . snapshots/Interface# +// ⌄ enclosing_range_start local 10 + override fun foo() {} +// ^^^ definition local 10 +// kind Method +// display_name foo +// signature_documentation +// > public open override fun foo(): Unit +// relationship scip-java maven . . snapshots/Interface#foo(). implementation +// ⌃ enclosing_range_end local 10 + } +// ⌃ enclosing_range_end local 7 +// ⌃ enclosing_range_end local 8 +// ⌃ enclosing_range_end local 9 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/anonymousObjects(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/outer(). + fun outer() { +// ^^^^^ definition scip-java maven . . snapshots/outer(). +// kind Method +// display_name outer +// signature_documentation +// > public final fun outer(): Unit +// ⌄ enclosing_range_start local 11 + fun inner() {} +// ^^^^^ definition local 11 +// kind Method +// display_name inner +// signature_documentation +// > local final fun inner(): Unit +// ⌃ enclosing_range_end local 11 +// ⌄ enclosing_range_start local 12 + fun innerWithReturnType(): Int = 42 +// ^^^^^^^^^^^^^^^^^^^ definition local 12 +// kind Method +// display_name innerWithReturnType +// signature_documentation +// > local final fun innerWithReturnType(): Int +// ^^^ reference scip-java maven . . kotlin/Int# +// ⌃ enclosing_range_end local 12 + inner() +// ^^^^^ reference local 11 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/outer(). + 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 6211a42e2..acd1b0fab 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 @@ -8,6 +8,7 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt# //⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#INSTANCE. object ObjectKt { // ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt# // kind Class @@ -19,6 +20,11 @@ // display_name ObjectKt // signature_documentation // > private constructor(): ObjectKt +// ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt#INSTANCE. +// kind StaticField +// display_name INSTANCE +// signature_documentation +// > public final static val INSTANCE: ObjectKt // ⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#fail(). // ⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#fail().(message) fun fail(message: String?): Nothing { @@ -32,15 +38,60 @@ // display_name message // signature_documentation // > message: String? -// ^^^^^^^ reference scip-java maven . . kotlin/String# +// ^^^^^^ reference scip-java maven . . kotlin/String# // ^^^^^^^ reference scip-java maven . . kotlin/Nothing# // ⌃ enclosing_range_end scip-java maven . . snapshots/ObjectKt#fail().(message) throw RuntimeException(message) -// ^^^^^^^^^^^^^^^^ reference scip-java maven jdk 17 java/lang/RuntimeException#``(). +// ^^^^^^^^^^^^^^^^ reference scip-java maven jdk 17 java/lang/RuntimeException#``(+1). // ^^^^^^^ reference scip-java maven . . snapshots/ObjectKt#fail().(message) } // ⌃ enclosing_range_end scip-java maven . . snapshots/ObjectKt#fail(). } //⌃ enclosing_range_end scip-java maven . . snapshots/ObjectKt# //⌃ enclosing_range_end scip-java maven . . snapshots/ObjectKt#``(). +//⌃ enclosing_range_end scip-java maven . . snapshots/ObjectKt#INSTANCE. + +//⌄ enclosing_range_start scip-java maven . . snapshots/MySingleton# +//⌄ enclosing_range_start scip-java maven . . snapshots/MySingleton#``(). +//⌄ enclosing_range_start scip-java maven . . snapshots/MySingleton#INSTANCE. + // Named objects define an INSTANCE field for Java callers; object expressions do not. + object MySingleton { +// ^^^^^^^^^^^ definition scip-java maven . . snapshots/MySingleton# +// kind Class +// display_name MySingleton +// signature_documentation +// > public final object MySingleton : Any +// ^^^^^^^^^^^ definition scip-java maven . . snapshots/MySingleton#``(). +// kind Constructor +// display_name MySingleton +// signature_documentation +// > private constructor(): MySingleton +// ^^^^^^^^^^^ definition scip-java maven . . snapshots/MySingleton#INSTANCE. +// kind StaticField +// display_name INSTANCE +// signature_documentation +// > public final static val INSTANCE: MySingleton +// ⌄ enclosing_range_start scip-java maven . . snapshots/MySingleton#hello(). + fun hello(): String = "hi" +// ^^^^^ definition scip-java maven . . snapshots/MySingleton#hello(). +// kind Method +// display_name hello +// signature_documentation +// > public final fun hello(): String +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end scip-java maven . . snapshots/MySingleton#hello(). + } +//⌃ enclosing_range_end scip-java maven . . snapshots/MySingleton# +//⌃ enclosing_range_end scip-java maven . . snapshots/MySingleton#``(). +//⌃ enclosing_range_end scip-java maven . . snapshots/MySingleton#INSTANCE. +//⌄ enclosing_range_start scip-java maven . . snapshots/useSingleton(). + fun useSingleton() = MySingleton.hello() +// ^^^^^^^^^^^^ definition scip-java maven . . snapshots/useSingleton(). +// kind Method +// display_name useSingleton +// signature_documentation +// > public final fun useSingleton(): String +// ^^^^^^^^^^^ reference scip-java maven . . snapshots/MySingleton# +// ^^^^^ reference scip-java maven . . snapshots/MySingleton#hello(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/useSingleton(). diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt new file mode 100644 index 000000000..d8c976934 --- /dev/null +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/TypeOperators.kt @@ -0,0 +1,93 @@ + package snapshots +// ^^^^^^^^^ reference scip-java maven . . snapshots/ + +//⌄ enclosing_range_start scip-java maven . . snapshots/typeOperators(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/typeOperators().(x) + fun typeOperators(x: Any) { +// ^^^^^^^^^^^^^ definition scip-java maven . . snapshots/typeOperators(). +// kind Method +// display_name typeOperators +// signature_documentation +// > public final fun typeOperators(x: Any): Unit +// ^ definition scip-java maven . . snapshots/typeOperators().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Any +// ^^^ reference scip-java maven . . kotlin/Any# +// ⌃ enclosing_range_end scip-java maven . . snapshots/typeOperators().(x) +// ⌄ enclosing_range_start local 0 + when (x) { +// ^ definition local 0 +// kind Property +// display_name +// signature_documentation +// > local val : Any +// ^ reference scip-java maven . . snapshots/typeOperators().(x) +// ⌃ enclosing_range_end local 0 + is Int -> true +// ^^^ reference scip-java maven . . kotlin/Int# + else -> x as Float +// ^ reference scip-java maven . . snapshots/typeOperators().(x) +// ^^^^^ reference scip-java maven . . kotlin/Float# + } + } +//⌃ enclosing_range_end scip-java maven . . snapshots/typeOperators(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/Wrapper# +//⌄ enclosing_range_start scip-java maven . . snapshots/Wrapper#``(). + class Wrapper +// ^^^^^^^ definition scip-java maven . . snapshots/Wrapper# +// kind Class +// display_name Wrapper +// signature_documentation +// > public final class Wrapper : Any +// ^^^^^^^ definition scip-java maven . . snapshots/Wrapper#``(). +// kind Constructor +// display_name Wrapper +// signature_documentation +// > public constructor(): Wrapper +// ⌃ enclosing_range_end scip-java maven . . snapshots/Wrapper# +// ⌃ enclosing_range_end scip-java maven . . snapshots/Wrapper#``(). + +//⌄ enclosing_range_start scip-java maven . . snapshots/classify(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/classify().(x) + fun classify(x: Any) { +// ^^^^^^^^ definition scip-java maven . . snapshots/classify(). +// kind Method +// display_name classify +// signature_documentation +// > public final fun classify(x: Any): Unit +// ^ definition scip-java maven . . snapshots/classify().(x) +// kind Parameter +// display_name x +// signature_documentation +// > x: Any +// ^^^ reference scip-java maven . . kotlin/Any# +// ⌃ enclosing_range_end scip-java maven . . snapshots/classify().(x) + if (x is Wrapper) {} +// ^ reference scip-java maven . . snapshots/classify().(x) +// ^^^^^^^ reference scip-java maven . . snapshots/Wrapper# +// ⌄ enclosing_range_start local 1 + val s = x as? String +// ^ definition local 1 +// kind Property +// display_name s +// signature_documentation +// > local val s: String? +// ^ reference scip-java maven . . snapshots/classify().(x) +// ^^^^^^ reference scip-java maven . . kotlin/String# +// ⌃ enclosing_range_end local 1 +// ⌄ enclosing_range_start local 2 + val w = x as Wrapper +// ^ definition local 2 +// kind Property +// display_name w +// signature_documentation +// > local val w: Wrapper +// ^ reference scip-java maven . . snapshots/classify().(x) +// ^^^^^^^ reference scip-java maven . . snapshots/Wrapper# +// ⌃ enclosing_range_end local 2 + } +//⌃ enclosing_range_end scip-java maven . . snapshots/classify(). + diff --git a/settings.gradle.kts b/settings.gradle.kts index 7a3395b07..6747622d7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,14 @@ dependencyResolutionManagement { repositories { mavenCentral() gradlePluginPortal() + // Kotlin Analysis API (`*-for-ide`) artifacts and the IntelliJ coroutines + // fork used by scip-kotlin-analysis. + maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") { + content { + includeGroupAndSubgroups("org.jetbrains.kotlin") + includeGroup("com.intellij.platform") + } + } } } @@ -19,7 +27,7 @@ rootProject.name = "scip-java" include( "scip-shared", "scip-javac", - "scip-kotlinc", + "scip-kotlin-analysis", "scip-aggregator", "scip-maven-plugin", "scip-gradle-plugin",