Analysis API: add isSubclassOf tests

This commit is contained in:
Ilya Kirillov
2021-12-31 19:36:53 +03:00
parent e6fc434489
commit 11c50a48ba
7 changed files with 183 additions and 14 deletions
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.fe10.components.symbolDeclarationOverridesProvider;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService;
import org.jetbrains.kotlin.analysis.api.descriptors.test.KtFe10FrontendApiTestConfiguratorService;
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider.AbstractIsSubclassOfTest;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf")
@TestDataPath("$PROJECT_ROOT")
public class Fe10IsSubclassOfTestGenerated extends AbstractIsSubclassOfTest {
@NotNull
@Override
public FrontendApiTestConfiguratorService getConfigurator() {
return KtFe10FrontendApiTestConfiguratorService.INSTANCE;
}
@Test
public void testAllFilesPresentInIsSubclassOf() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("localClasses.kt")
public void testLocalClasses() throws Exception {
runTest("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf/localClasses.kt");
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.fir.components.symbolDeclarationOverridesProvider;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService;
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService;
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider.AbstractIsSubclassOfTest;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf")
@TestDataPath("$PROJECT_ROOT")
public class FirIsSubclassOfTestGenerated extends AbstractIsSubclassOfTest {
@NotNull
@Override
public FrontendApiTestConfiguratorService getConfigurator() {
return FirFrontendApiTestConfiguratorService.INSTANCE;
}
@Test
public void testAllFilesPresentInIsSubclassOf() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("localClasses.kt")
public void testLocalClasses() throws Exception {
runTest("analysis/analysis-api/testData/components/symbolDeclarationOverridesProvider/isSubclassOf/localClasses.kt");
}
}
@@ -41,17 +41,22 @@ internal class ExpressionMarkersSourceFilePreprocessor(testServices: TestService
} }
private fun processCaretExpression(file: TestFile, content: String): String { private fun processCaretExpression(file: TestFile, content: String): String {
val startCaretPosition = content.indexOfOrNull(TAGS.CARET) ?: return content var result = content
var match = TAGS.CARET_REGEXP.find(result)
testServices.expressionMarkerProvider.addCaret(file, startCaretPosition) while (match != null) {
return content val startCaretPosition = match.range.first
.replace(TAGS.CARET, "") val tag = match.groups[2]?.value
testServices.expressionMarkerProvider.addCaret(file, tag, startCaretPosition)
result = result.removeRange(match.range)
match = TAGS.CARET_REGEXP.find(result)
}
return result
} }
object TAGS { object TAGS {
const val OPENING_EXPRESSION_TAG = "<expr>" const val OPENING_EXPRESSION_TAG = "<expr>"
const val CLOSING_EXPRESSION_TAG = "</expr>" const val CLOSING_EXPRESSION_TAG = "</expr>"
const val CARET = "<caret>" val CARET_REGEXP = "<caret(_(\\w+))?>".toRegex()
} }
} }
@@ -59,25 +64,29 @@ class ExpressionMarkerProvider : TestService {
private val selected = mutableMapOf<String, TextRange>() private val selected = mutableMapOf<String, TextRange>()
@PrivateForInline @PrivateForInline
val atCaret = mutableMapOf<String, Int>() val carets = CaretProvider()
fun addSelectedExpression(file: TestFile, range: TextRange) { fun addSelectedExpression(file: TestFile, range: TextRange) {
selected[file.relativePath] = range selected[file.relativePath] = range
} }
@OptIn(PrivateForInline::class) @OptIn(PrivateForInline::class)
fun addCaret(file: TestFile, caret: Int) { fun addCaret(file: TestFile, caretTag: String?, caretOffset: Int) {
atCaret[file.relativePath] = caret carets.addCaret(file.name, caretTag, caretOffset)
} }
@OptIn(PrivateForInline::class) @OptIn(PrivateForInline::class)
fun getCaretPosition(file: KtFile): Int { fun getCaretPosition(file: KtFile, caretTag: String? = null): Int {
return atCaret[file.name] return carets.getCaretOffset(file.name, caretTag)
?: error("No caret found in file") ?: run {
val caretName = "caret${caretTag?.let { "_$it" }.orEmpty()}"
error("No <$caretName> found in file")
}
} }
inline fun <reified P : KtElement> getElementOfTypAtCaret(file: KtFile): P { inline fun <reified P : KtElement> getElementOfTypAtCaret(file: KtFile, caretName: String? = null): P {
val offset = getCaretPosition(file) val offset = getCaretPosition(file, caretName)
return file.findElementAt(offset) return file.findElementAt(offset)
?.parentOfType<P>() ?.parentOfType<P>()
?: error("No expression found at caret") ?: error("No expression found at caret")
@@ -98,6 +107,33 @@ class ExpressionMarkerProvider : TestService {
.dropLastWhile { it is PsiWhiteSpace } .dropLastWhile { it is PsiWhiteSpace }
} }
@PrivateForInline
class CaretProvider {
private val caretToFile = mutableMapOf<String, CaretsInFile>()
fun getCaretOffset(filename: String, caretTag: String?): Int? {
val cartsInFile = caretToFile[filename] ?: return null
return cartsInFile.getCaretOffsetByTag(caretTag)
}
fun addCaret(filename: String, caretTag: String?, caretOffset: Int) {
val cartsInFile = caretToFile.getOrPut(filename) { CaretsInFile() }
cartsInFile.addCaret(caretTag, caretOffset)
}
private class CaretsInFile {
private val carets = mutableMapOf<String, Int>()
fun getCaretOffsetByTag(tag: String?): Int? {
return carets[tag.orEmpty()]
}
fun addCaret(caretTag: String?, caretOffset: Int) {
carets[caretTag.orEmpty()] = caretOffset
}
}
}
val TestServices.expressionMarkerProvider: ExpressionMarkerProvider by TestServices.testServiceAccessor() val TestServices.expressionMarkerProvider: ExpressionMarkerProvider by TestServices.testServiceAccessor()
fun String.indexOfOrNull(substring: String) = fun String.indexOfOrNull(substring: String) =
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
abstract class AbstractIsSubclassOfTest : AbstractHLApiSingleModuleTest() {
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val subClass = testServices.expressionMarkerProvider.getElementOfTypAtCaret<KtClassOrObject>(ktFiles.first(), "sub")
val superClass = testServices.expressionMarkerProvider.getElementOfTypAtCaret<KtClassOrObject>(ktFiles.first(), "super")
val actual = executeOnPooledThreadInReadAction {
analyseForTest(subClass) {
val subClassSymbol = subClass.getClassOrObjectSymbol()
val superClassSymbol = superClass.getClassOrObjectSymbol()
val isSubClass = subClassSymbol.isSubClassOf(superClassSymbol)
val isDirectSubClass = subClassSymbol.isDirectSubClassOf(superClassSymbol)
buildString {
appendLine("IS_SUBCLASS: $isSubClass")
appendLine("IS_DIRECT_SUBCLASS: $isDirectSubClass")
}
}
}
testServices.assertions.assertEqualsToTestDataFileSibling(actual)
}
}
@@ -0,0 +1,5 @@
fun foo() {
open class <caret_super>A
open class <caret_sub>T : A()
}
@@ -0,0 +1,2 @@
IS_SUBCLASS: true
IS_DIRECT_SUBCLASS: true
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.analysis.api.impl.base.test.components.expressionTyp
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.expressionTypeProvider.AbstractExpectedExpressionTypeTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.expressionTypeProvider.AbstractExpectedExpressionTypeTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.expressionTypeProvider.AbstractHLExpressionTypeTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.expressionTypeProvider.AbstractHLExpressionTypeTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.smartCastProvider.AbstractHLSmartCastInfoTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.smartCastProvider.AbstractHLSmartCastInfoTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider.AbstractIsSubclassOfTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider.AbstractOverriddenDeclarationProviderTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationOverridesProvider.AbstractOverriddenDeclarationProviderTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationRenderer.AbstractRendererTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.symbolDeclarationRenderer.AbstractRendererTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.typeCreator.AbstractTypeParameterTypeTest import org.jetbrains.kotlin.analysis.api.impl.base.test.components.typeCreator.AbstractTypeParameterTypeTest
@@ -195,6 +196,10 @@ private fun TestGroupSuite.generateAnalysisApiComponentsTests() {
test(AbstractOverriddenDeclarationProviderTest::class) { test(AbstractOverriddenDeclarationProviderTest::class) {
model("overriddenSymbols") model("overriddenSymbols")
} }
test(AbstractIsSubclassOfTest::class) {
model("isSubclassOf")
}
} }
component("symbolDeclarationRenderer") { component("symbolDeclarationRenderer") {