Skip to content

Commit

Permalink
Scan Kotlin constants for JavaPsiFacade
Browse files Browse the repository at this point in the history
so that constants in light classes can be resolved by JavaPsiFacade.
  • Loading branch information
ting-yuan committed Oct 6, 2022
1 parent a634e55 commit d85eb20
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 0 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -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<KSFileImpl>().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(
Expand Down
@@ -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")
}
}
@@ -0,0 +1,8 @@
plugins {
kotlin("jvm")
}

repositories {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/")
}
@@ -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")
@@ -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")
}
@@ -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<String, String>,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver
.getSymbolsWithAnnotation("com.example.ann.MyAnn")
.filterIsInstance<KSFunctionDeclaration>()
.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)
}
@@ -0,0 +1 @@
TestProcessorProvider
@@ -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"))
}
@@ -0,0 +1,9 @@
package com.example;

import com.example.ann.MyAnn;

public class JavaClass {
@MyAnn(KotlinConsts.ACTION)
public void f() {
}
}
@@ -0,0 +1,8 @@
package com.example

class KotlinConsts {
companion object {
const val ACTION = "REPLACE"
const val ACTION2 = "REPLACE"
}
}
@@ -0,0 +1,3 @@
package com.example.ann

annotation class MyAnn(val value: String)

0 comments on commit d85eb20

Please sign in to comment.