Java: make JavaApiUsageGenerator more robust

- only check that parameters are fully qualified when the signature is actually used (otherwise generating api for jdk 25 ea fails)
- fail when no source files are found instead of completing successfully without output
- remove PREVIEW_JDK_HOME
- use sensible default for SINCE_VERSION
- improve documentation

GitOrigin-RevId: 708959c0f82da37599d32e5c8edbfdb7c0aac47b
This commit is contained in:
Bas Leijdekkers
2025-06-11 15:24:52 +02:00
committed by intellij-monorepo-bot
parent 215f6129cf
commit cdaae6b8bf

View File

@@ -3,7 +3,6 @@ package com.intellij.jvm.analysis.internal.testFramework
import com.intellij.java.codeserver.core.JavaPreviewFeatureUtil
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.JDK_HOME
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.LANGUAGE_LEVEL
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.PREVIEW_JDK_HOME
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.SINCE_VERSION
import com.intellij.openapi.module.JdkApiCompatibilityService
import com.intellij.openapi.projectRoots.JavaSdk
@@ -31,15 +30,45 @@ import kotlin.io.path.*
/**
* Generator which is used to generate required api files for [com.intellij.codeInspection.JavaApiUsageInspection].
* To generate new API lists for next release, you will need to set [JDK_HOME], [LANGUAGE_LEVEL], [SINCE_VERSION] and then run
* [testCollectSinceApiUsages].
* [testCollectSinceApiUsages]. The API list will be printed on standard out.
*/
@Ignore
class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): LightProjectDescriptor = object : ProjectDescriptor(LANGUAGE_LEVEL) {
override fun getSdk(): Sdk {
val sdk = SdkTableImplementationDelegate.getInstance().createSdk("java-gen", JavaSdk.getInstance(), JDK_HOME)
JavaSdk.getInstance().setupSdkPaths(sdk)
return sdk
companion object {
/**
* Absolute path to the jdk where to find the Java source files to create an API list for. On MacOS include /Contents/Home at the end.
*/
private const val JDK_HOME = "/Library/Java/JavaVirtualMachines/jdk-24.jdk/Contents/Home"
/**
* The language level used to parse the source files.
*/
private val LANGUAGE_LEVEL = LanguageLevel.JDK_24
/**
* The @since tag value to find API's for. By default, matches [LANGUAGE_LEVEL].
*/
private val SINCE_VERSION = LANGUAGE_LEVEL.feature().toString()
/**
* Used in [testGenerateRemovedEntries]
*/
private const val TEMP_API_DIR = "REPLACE_ME"
/**
* Dir to API lists. Used in [testGenerateRemovedEntries] and [testRemoveNonPublicApi]
*/
private const val API_DIR = "REPLACE_ME"
}
/**
* Generates an API list for the specified [JDK_HOME], [LANGUAGE_LEVEL] and [SINCE_VERSION].
*/
fun testCollectSinceApiUsages() {
IdeaTestUtil.withLevel(myFixture.module, LANGUAGE_LEVEL) {
doCollectSinceApiUsages()
}
}
@@ -53,6 +82,20 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
}
}
fun testRemoveNonPublicApi() {
IdeaTestUtil.withLevel(myFixture.module, LANGUAGE_LEVEL) {
filterSignatures(Path.of(API_DIR)) { isPublicApi() }
}
}
override fun getProjectDescriptor(): LightProjectDescriptor = object : ProjectDescriptor(LANGUAGE_LEVEL) {
override fun getSdk(): Sdk {
val sdk = SdkTableImplementationDelegate.getInstance().createSdk("java-gen", JavaSdk.getInstance(), JDK_HOME)
JavaSdk.getInstance().setupSdkPaths(sdk)
return sdk
}
}
private fun removedEntries(current: Path, previous: Path) {
val result = Files.createDirectory(previous.resolve("result"))
current.listDirectoryEntries().filter { it.name.startsWith("api") && it.extension == "txt" }.forEach { currentEntry ->
@@ -97,12 +140,6 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
return parentMember.isPublicApi()
}
fun testRemoveNonPublicApi() {
IdeaTestUtil.withLevel(myFixture.module, LANGUAGE_LEVEL) {
filterSignatures(Path.of(API_DIR)) { isPublicApi() }
}
}
/**
* Can be used to filter out API lists from the [path] according to the provided [filter].
*/
@@ -147,19 +184,13 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
} else clazz
}
fun testCollectSinceApiUsages() {
IdeaTestUtil.withLevel(myFixture.module, LANGUAGE_LEVEL) {
doCollectSinceApiUsages()
}
}
fun getParamFqns(signature: String): List<String> {
return signature.substringAfter("(").substringBefore(")").split(";").dropLast(1)
}
/**
* Run to generate API lists.
* Setting [LANGUAGE_LEVEL], [SINCE_VERSION] and [JDK_HOME] or [PREVIEW_JDK_HOME] is required.
* Setting [LANGUAGE_LEVEL], [SINCE_VERSION] and [JDK_HOME] is required.
*/
private fun doCollectSinceApiUsages() {
val previews = mutableSetOf<String>()
@@ -185,9 +216,10 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
return feature?.minimumLevel
}
}
val srcFile = JarFileSystem.getInstance().findFileByPath("$JDK_HOME/lib/src.zip!/")
?: throw IllegalStateException("JDK source files not found in $JDK_HOME")
if (LANGUAGE_LEVEL.isPreview) {
val previewSrcFile = JarFileSystem.getInstance().findFileByPath("$PREVIEW_JDK_HOME/lib/src.zip!/") ?: return
VfsUtilCore.iterateChildrenRecursively(previewSrcFile, VirtualFileFilter.ALL, previewContentIterator)
VfsUtilCore.iterateChildrenRecursively(srcFile, VirtualFileFilter.ALL, previewContentIterator)
}
val contentIterator = ContentIterator { fileOrDir ->
val file = PsiManager.getInstance(project).findFile(fileOrDir) as? PsiJavaFile
@@ -200,11 +232,8 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
if (JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) == null) {
return // If the class is not in all scope, don't generate
}
val paramFqns = getParamFqns(signature).map { name -> name.substringBefore("[").substringBefore("<") }
if (paramFqns.any { name -> !isValidTypeName(element, name) }) {
throw IllegalStateException("Generated parameters $paramFqns must be fully qualified or primitive")
}
if (isDocumentedSinceApi(element) && !previews.contains(signature)) {
checkParametersAreFullyQualified(signature, element)
println(signature)
} else if (element is PsiMethod && element.docComment == null) { // find inherited doc
val sinceSuperVersions = element.findDeepestSuperMethods().map { superMethod ->
@@ -214,19 +243,26 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
}
val sinceVersion = sinceSuperVersions.filterNotNull().minOrNull()
if (sinceVersion == LANGUAGE_LEVEL.toJavaVersion()) {
checkParametersAreFullyQualified(signature, element)
println(signature)
}
}
}
}
private fun checkParametersAreFullyQualified(signature: String, element: PsiMember) {
val paramFqns = getParamFqns(signature).map { name -> name.substringBefore("[").substringBefore("<") }
if (paramFqns.any { name -> !isValidTypeName(element, name) }) {
throw IllegalStateException("Generated parameters must be fully qualified or primitive: $signature")
}
}
fun isDocumentedSinceApi(element: PsiElement): Boolean = (element as? PsiDocCommentOwner)?.docComment?.tags?.any { tag ->
tag.name == "since" && tag.valueElement?.text == SINCE_VERSION
} == true
})
true
}
val srcFile = JarFileSystem.getInstance().findFileByPath("$JDK_HOME/lib/src.zip!/") ?: return
VfsUtilCore.iterateChildrenRecursively(srcFile, VirtualFileFilter.ALL, contentIterator)
}
@@ -241,27 +277,4 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
val parent = element.parentOfType<PsiMember>() ?: return false
return isValidTypeName(parent, name)
}
companion object {
private const val TEMP_API_DIR = "REPLACE_ME"
/**
* Dir to API lists
*/
private const val API_DIR = "REPLACE_ME"
private const val PREVIEW_JDK_HOME = "/home/me/.jdks/openjdk-20"
private const val JDK_HOME = "/home/me/.jdks/openjdk-20"
/**
* The language level to check for.
*/
private val LANGUAGE_LEVEL = LanguageLevel.JDK_24
/**
* The @since tag value used should match [LANGUAGE_LEVEL].
*/
private const val SINCE_VERSION = "24"
}
}