[FIR] Correctly process slash in class name in ClassId
^KTIJ-27358 fixed Merge-request: KT-MR-12673 Merged-by: Egor Kulikov <Egor.Kulikov@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
/* ClassId: /`test/` */class `test/`
|
||||||
|
/* ClassId: /`test/test` */class `test/test`
|
||||||
+46
@@ -5,21 +5,28 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.analysis.low.level.api.fir
|
package org.jetbrains.kotlin.analysis.low.level.api.fir
|
||||||
|
|
||||||
|
import com.intellij.psi.stubs.StubInputStream
|
||||||
|
import com.intellij.psi.stubs.StubOutputStream
|
||||||
|
import com.intellij.util.io.AbstractStringEnumerator
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.AbstractClassIdConsistencyTest.Directives.IGNORE_CONSISTENCY_CHECK
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.AbstractClassIdConsistencyTest.Directives.IGNORE_CONSISTENCY_CHECK
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirScriptTestConfigurator
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirScriptTestConfigurator
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator
|
||||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
|
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
|
||||||
import org.jetbrains.kotlin.analysis.test.framework.utils.ignoreExceptionIfIgnoreDirectivePresent
|
import org.jetbrains.kotlin.analysis.test.framework.utils.ignoreExceptionIfIgnoreDirectivePresent
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.psi.KtClassLikeDeclaration
|
import org.jetbrains.kotlin.psi.KtClassLikeDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.safeFqNameForLazyResolve
|
import org.jetbrains.kotlin.psi.psiUtil.safeFqNameForLazyResolve
|
||||||
|
import org.jetbrains.kotlin.psi.stubs.StubUtils
|
||||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||||
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
|
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
|
||||||
import org.jetbrains.kotlin.test.services.TestModuleStructure
|
import org.jetbrains.kotlin.test.services.TestModuleStructure
|
||||||
import org.jetbrains.kotlin.test.services.TestServices
|
import org.jetbrains.kotlin.test.services.TestServices
|
||||||
import org.jetbrains.kotlin.test.services.assertions
|
import org.jetbrains.kotlin.test.services.assertions
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
abstract class AbstractClassIdConsistencyTest : AbstractLowLevelApiSingleFileTest() {
|
abstract class AbstractClassIdConsistencyTest : AbstractLowLevelApiSingleFileTest() {
|
||||||
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
|
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
|
||||||
@@ -28,10 +35,49 @@ abstract class AbstractClassIdConsistencyTest : AbstractLowLevelApiSingleFileTes
|
|||||||
val classId = declaration.getClassId()
|
val classId = declaration.getClassId()
|
||||||
val fqName = declaration.safeFqNameForLazyResolve()
|
val fqName = declaration.safeFqNameForLazyResolve()
|
||||||
testServices.assertions.assertEquals(fqName, classId?.asSingleFqName())
|
testServices.assertions.assertEquals(fqName, classId?.asSingleFqName())
|
||||||
|
|
||||||
|
testSerialization(classId, testServices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun testSerialization(classId: ClassId?, testServices: TestServices) {
|
||||||
|
val outStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
val enumerator = object : AbstractStringEnumerator {
|
||||||
|
var list: MutableList<String?> = mutableListOf(null)
|
||||||
|
var map: MutableMap<String?, Int> = mutableMapOf(null to 0)
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDirty() = false
|
||||||
|
|
||||||
|
override fun force() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enumerate(value: String?): Int {
|
||||||
|
return map.getOrElse(value) {
|
||||||
|
map[value] = list.size
|
||||||
|
list += value
|
||||||
|
list.size - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun valueOf(idx: Int): String? {
|
||||||
|
return list[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun markCorrupted() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StubUtils.serializeClassId(StubOutputStream(outStream, enumerator), classId)
|
||||||
|
val inStream = ByteArrayInputStream(outStream.toByteArray())
|
||||||
|
val result = StubUtils.deserializeClassId(StubInputStream(inStream, enumerator))
|
||||||
|
testServices.assertions.assertEquals(classId, result)
|
||||||
|
}
|
||||||
|
|
||||||
private object Directives : SimpleDirectivesContainer() {
|
private object Directives : SimpleDirectivesContainer() {
|
||||||
val IGNORE_CONSISTENCY_CHECK by stringDirective("Temporary disable test until the issue is fixed")
|
val IGNORE_CONSISTENCY_CHECK by stringDirective("Temporary disable test until the issue is fixed")
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -42,6 +42,12 @@ public class SourceClassIdConsistencyTestGenerated extends AbstractSourceClassId
|
|||||||
runTest("analysis/low-level-api-fir/testData/classId/enumEntry.kt");
|
runTest("analysis/low-level-api-fir/testData/classId/enumEntry.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("invalidForJvmClassName.kt")
|
||||||
|
public void testInvalidForJvmClassName() throws Exception {
|
||||||
|
runTest("analysis/low-level-api-fir/testData/classId/invalidForJvmClassName.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("localClassInsideFunctionLiteral.kt")
|
@TestMetadata("localClassInsideFunctionLiteral.kt")
|
||||||
public void testLocalClassInsideFunctionLiteral() throws Exception {
|
public void testLocalClassInsideFunctionLiteral() throws Exception {
|
||||||
|
|||||||
+6
@@ -42,6 +42,12 @@ public class SourceClassIdTestGenerated extends AbstractSourceClassIdTest {
|
|||||||
runTest("analysis/low-level-api-fir/testData/classId/enumEntry.kt");
|
runTest("analysis/low-level-api-fir/testData/classId/enumEntry.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("invalidForJvmClassName.kt")
|
||||||
|
public void testInvalidForJvmClassName() throws Exception {
|
||||||
|
runTest("analysis/low-level-api-fir/testData/classId/invalidForJvmClassName.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("localClassInsideFunctionLiteral.kt")
|
@TestMetadata("localClassInsideFunctionLiteral.kt")
|
||||||
public void testLocalClassInsideFunctionLiteral() throws Exception {
|
public void testLocalClassInsideFunctionLiteral() throws Exception {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ object KotlinStubVersions {
|
|||||||
// Though only kotlin declarations (no code in the bodies) are stubbed, please do increase this version
|
// Though only kotlin declarations (no code in the bodies) are stubbed, please do increase this version
|
||||||
// if you are not 100% sure it can be avoided.
|
// if you are not 100% sure it can be avoided.
|
||||||
// Increasing this version will lead to reindexing of all kotlin source files on the first IDE startup with the new version.
|
// Increasing this version will lead to reindexing of all kotlin source files on the first IDE startup with the new version.
|
||||||
const val SOURCE_STUB_VERSION = 157
|
const val SOURCE_STUB_VERSION = 158
|
||||||
|
|
||||||
// Binary stub version should be increased if stub format (org.jetbrains.kotlin.psi.stubs.impl) is changed
|
// Binary stub version should be increased if stub format (org.jetbrains.kotlin.psi.stubs.impl) is changed
|
||||||
// or changes are made to the core stub building code (org.jetbrains.kotlin.idea.decompiler.stubBuilder).
|
// or changes are made to the core stub building code (org.jetbrains.kotlin.idea.decompiler.stubBuilder).
|
||||||
|
|||||||
@@ -75,13 +75,21 @@ data class ClassId(val packageFqName: FqName, val relativeClassName: FqName, val
|
|||||||
* @return a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
|
* @return a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
|
||||||
*/
|
*/
|
||||||
fun asString(): String {
|
fun asString(): String {
|
||||||
|
fun FqName.escapeSlashes(): String {
|
||||||
|
val res = asString()
|
||||||
|
if (res.contains('/')) {
|
||||||
|
return "`$res`"
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
return if (packageFqName.isRoot) {
|
return if (packageFqName.isRoot) {
|
||||||
relativeClassName.asString()
|
relativeClassName.escapeSlashes()
|
||||||
} else {
|
} else {
|
||||||
buildString {
|
buildString {
|
||||||
append(packageFqName.asString().replace('.', '/'))
|
append(packageFqName.asString().replace('.', '/'))
|
||||||
append("/")
|
append("/")
|
||||||
append(relativeClassName.asString())
|
append(relativeClassName.escapeSlashes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,20 +117,22 @@ data class ClassId(val packageFqName: FqName, val relativeClassName: FqName, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
|
* @param string a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry".
|
||||||
|
* If class name contains slashes, it should be put into ticks, e.g. "package/`test/test`"
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromString(string: String, isLocal: Boolean = false): ClassId {
|
fun fromString(string: String, isLocal: Boolean = false): ClassId {
|
||||||
val lastSlashIndex = string.lastIndexOf("/")
|
val tickIndex = string.indexOf('`')
|
||||||
|
val lastSlashIndex = string.lastIndexOf("/", if (tickIndex == -1) string.length else tickIndex)
|
||||||
val packageName: String
|
val packageName: String
|
||||||
val className: String
|
val className: String
|
||||||
if (lastSlashIndex == -1) {
|
if (lastSlashIndex == -1) {
|
||||||
packageName = ""
|
packageName = ""
|
||||||
className = string
|
className = string.replace("`", "")
|
||||||
} else {
|
} else {
|
||||||
packageName = string.substring(0, lastSlashIndex).replace('/', '.')
|
packageName = string.substring(0, lastSlashIndex).replace('/', '.')
|
||||||
className = string.substring(lastSlashIndex + 1)
|
className = string.substring(lastSlashIndex + 1).replace("`", "")
|
||||||
}
|
}
|
||||||
return ClassId(FqName(packageName), FqName(className), isLocal)
|
return ClassId(FqName(packageName), FqName(className), isLocal)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user