From d85eb20b54ac7c414ee02f088ef2fbbdfc51f823 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Thu, 6 Oct 2022 01:29:32 -0700 Subject: [PATCH] Scan Kotlin constants for JavaPsiFacade so that constants in light classes can be resolved by JavaPsiFacade. --- .../ksp/KotlinSymbolProcessingExtension.kt | 42 +++++++++++++++++++ .../devtools/ksp/test/KotlinConstsInJavaIT.kt | 42 +++++++++++++++++++ .../kotlin-consts-in-java/build.gradle.kts | 8 ++++ .../kotlin-consts-in-java/settings.gradle.kts | 19 +++++++++ .../test-processor/build.gradle.kts | 24 +++++++++++ .../src/main/kotlin/TestProcessor.kt | 27 ++++++++++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + .../workload/build.gradle.kts | 20 +++++++++ .../src/main/java/com/example/JavaClass.java | 9 ++++ .../src/main/java/com/example/KotlinConsts.kt | 8 ++++ .../src/main/java/com/example/ann/MyAnn.kt | 3 ++ 11 files changed, 203 insertions(+) create mode 100644 integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/build.gradle.kts create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/settings.gradle.kts create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/build.gradle.kts create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/kotlin/TestProcessor.kt create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/workload/build.gradle.kts create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/JavaClass.java create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/KotlinConsts.kt create mode 100644 integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/ann/MyAnn.kt diff --git a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/KotlinSymbolProcessingExtension.kt b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/KotlinSymbolProcessingExtension.kt index f912513c7d..dbdf6c372b 100644 --- a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/KotlinSymbolProcessingExtension.kt +++ b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/KotlinSymbolProcessingExtension.kt @@ -31,8 +31,14 @@ import com.google.devtools.ksp.processing.impl.NativePlatformInfoImpl import com.google.devtools.ksp.processing.impl.ResolverImpl import com.google.devtools.ksp.processing.impl.UnknownPlatformInfoImpl import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclarationContainer import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSVisitorVoid +import com.google.devtools.ksp.symbol.Modifier import com.google.devtools.ksp.symbol.Origin +import com.google.devtools.ksp.symbol.Visibility import com.google.devtools.ksp.symbol.impl.java.KSFileJavaImpl import com.google.devtools.ksp.symbol.impl.kotlin.KSFileImpl import com.intellij.openapi.project.Project @@ -198,6 +204,42 @@ abstract class AbstractKotlinSymbolProcessingExtension( newFiles, deferredSymbols, bindingTrace, project, componentProvider, incrementalContext, options ) + if (!initialized) { + // Visit constants so that JavaPsiFacade knows them. + // The annotation visitor in ResolverImpl covered newFiles already. + // Skip private and local members, which are not visible to Java files. + ksFiles.filterIsInstance().filter { it !in dirtyFiles }.forEach { + try { + it.accept( + object : KSVisitorVoid() { + private fun visitDeclarationContainer(container: KSDeclarationContainer) { + container.declarations.filterNot { + it.getVisibility() == Visibility.PRIVATE + }.forEach { + it.accept(this, Unit) + } + } + + override fun visitFile(file: KSFile, data: Unit) = + visitDeclarationContainer(file) + + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) = + visitDeclarationContainer(classDeclaration) + + override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit) { + if (property.modifiers.contains(Modifier.CONST)) { + property.getter // force resolution + } + } + }, + Unit + ) + } catch (_: Exception) { + // Do nothing. + } + } + } + val providers = loadProviders() if (!initialized) { codeGenerator = CodeGeneratorImpl( diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt new file mode 100644 index 0000000000..d8714bf7b4 --- /dev/null +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt @@ -0,0 +1,42 @@ +package com.google.devtools.ksp.test + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Assert +import org.junit.Assume +import org.junit.Rule +import org.junit.Test +import java.io.File + +class KotlinConstsInJavaIT { + @Rule + @JvmField + val project: TemporaryTestProject = TemporaryTestProject("kotlin-consts-in-java") + + private fun GradleRunner.buildAndCheck(vararg args: String, extraCheck: (BuildResult) -> Unit = {}) = + buildAndCheckOutcome(*args, outcome = TaskOutcome.SUCCESS, extraCheck = extraCheck) + + private fun GradleRunner.buildAndCheckOutcome( + vararg args: String, + outcome: TaskOutcome, + extraCheck: (BuildResult) -> Unit = {} + ) { + val result = this.withArguments(*args).build() + + Assert.assertEquals(outcome, result.task(":workload:kspKotlin")?.outcome) + + extraCheck(result) + } + + @Test + fun testKotlinConstsInJava() { + // FIXME: `clean` fails to delete files on windows. + Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true)) + val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true) + gradleRunner.buildAndCheck(":workload:kspKotlin") + + File(project.root, "workload/src/main/java/com/example/JavaClass.java").appendText("\n") + gradleRunner.buildAndCheck(":workload:kspKotlin") + } +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/build.gradle.kts b/integration-tests/src/test/resources/kotlin-consts-in-java/build.gradle.kts new file mode 100644 index 0000000000..c5737a2e0d --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/settings.gradle.kts b/integration-tests/src/test/resources/kotlin-consts-in-java/settings.gradle.kts new file mode 100644 index 0000000000..9d60cbc72f --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + val kotlinVersion: String by settings + val kspVersion: String by settings + val testRepo: String by settings + plugins { + id("com.google.devtools.ksp") version kspVersion + kotlin("jvm") version kotlinVersion + } + repositories { + maven(testRepo) + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") + } +} + +rootProject.name = "playground" + +include(":workload") +include(":test-processor") diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/build.gradle.kts b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/build.gradle.kts new file mode 100644 index 0000000000..ab249cf06f --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/build.gradle.kts @@ -0,0 +1,24 @@ +val kspVersion: String by project +val testRepo: String by project + +plugins { + kotlin("jvm") +} + +group = "com.example" +version = "1.0-SNAPSHOT" + +repositories { + maven(testRepo) + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion") +} + +sourceSets.main { + java.srcDirs("src/main/kotlin") +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/kotlin/TestProcessor.kt b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/kotlin/TestProcessor.kt new file mode 100644 index 0000000000..7e14192745 --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/kotlin/TestProcessor.kt @@ -0,0 +1,27 @@ +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFunctionDeclaration + +class TestProcessor( + private val codeGenerator: CodeGenerator, + private val options: Map, + private val logger: KSPLogger +) : SymbolProcessor { + override fun process(resolver: Resolver): List { + resolver + .getSymbolsWithAnnotation("com.example.ann.MyAnn") + .filterIsInstance() + .forEach { func -> + val arg = func.annotations.first().arguments.first().value.toString() + if (!arg.startsWith("REPLACE")) + throw IllegalStateException(arg) + } + + return emptyList() + } +} + +class TestProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + TestProcessor(environment.codeGenerator, environment.options, environment.logger) +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000000..c91e3e9e0b --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +TestProcessorProvider diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/workload/build.gradle.kts b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/build.gradle.kts new file mode 100644 index 0000000000..f0ea52b0e3 --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/build.gradle.kts @@ -0,0 +1,20 @@ +val testRepo: String by project + +plugins { + id("com.google.devtools.ksp") + kotlin("jvm") +} + +version = "1.0-SNAPSHOT" + +repositories { + maven(testRepo) + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(project(":test-processor")) + ksp(project(":test-processor")) +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/JavaClass.java b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/JavaClass.java new file mode 100644 index 0000000000..a196f754d5 --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/JavaClass.java @@ -0,0 +1,9 @@ +package com.example; + +import com.example.ann.MyAnn; + +public class JavaClass { + @MyAnn(KotlinConsts.ACTION) + public void f() { + } +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/KotlinConsts.kt b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/KotlinConsts.kt new file mode 100644 index 0000000000..e5231980fd --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/KotlinConsts.kt @@ -0,0 +1,8 @@ +package com.example + +class KotlinConsts { + companion object { + const val ACTION = "REPLACE" + const val ACTION2 = "REPLACE" + } +} diff --git a/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/ann/MyAnn.kt b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/ann/MyAnn.kt new file mode 100644 index 0000000000..6812ee6350 --- /dev/null +++ b/integration-tests/src/test/resources/kotlin-consts-in-java/workload/src/main/java/com/example/ann/MyAnn.kt @@ -0,0 +1,3 @@ +package com.example.ann + +annotation class MyAnn(val value: String)