[Analysis API] Shortener: enable shortening in KDoc
^KTIJ-21103
This commit is contained in:
committed by
teamcity
parent
e7c213e06e
commit
67c3849538
+114
-11
@@ -15,11 +15,14 @@ import org.jetbrains.kotlin.analysis.api.components.ShortenOption
|
||||
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.fir.components.ElementsToShortenCollector.PartialOrderOfScope.Companion.toPartialOrder
|
||||
import org.jetbrains.kotlin.analysis.api.fir.isImplicitDispatchReceiver
|
||||
import org.jetbrains.kotlin.analysis.api.fir.references.KDocReferenceResolver
|
||||
import org.jetbrains.kotlin.analysis.api.fir.utils.FirBodyReanalyzingVisitorVoid
|
||||
import org.jetbrains.kotlin.analysis.api.fir.utils.computeImportableName
|
||||
import org.jetbrains.kotlin.analysis.api.fir.utils.firSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LowLevelFirApiFacadeForResolveOnAir
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFir
|
||||
@@ -56,11 +59,13 @@ import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
|
||||
import org.jetbrains.kotlin.name.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
|
||||
internal class KtFirReferenceShortener(
|
||||
override val analysisSession: KtFirAnalysisSession,
|
||||
@@ -77,15 +82,16 @@ internal class KtFirReferenceShortener(
|
||||
): ShortenCommand {
|
||||
require(!file.isCompiled) { "No sense to collect references for shortening in compiled file $file" }
|
||||
|
||||
val declarationToVisit = file.findSmallestDeclarationContainingSelection(selection)
|
||||
val declarationToVisit = file.findSmallestElementOfTypeContainingSelection<KtDeclaration>(selection)
|
||||
?: file
|
||||
|
||||
val firDeclaration = declarationToVisit.getOrBuildFir(firResolveSession) as? FirDeclaration ?: return ShortenCommandImpl(
|
||||
file.createSmartPointer(),
|
||||
importsToAdd = emptyList(),
|
||||
starImportsToAdd = emptyList(),
|
||||
importsToAdd = emptySet(),
|
||||
starImportsToAdd = emptySet(),
|
||||
typesToShorten = emptyList(),
|
||||
qualifiersToShorten = emptyList(),
|
||||
kDocQualifiersToShorten = emptyList(),
|
||||
)
|
||||
|
||||
val towerContext = when (declarationToVisit) {
|
||||
@@ -102,25 +108,114 @@ internal class KtFirReferenceShortener(
|
||||
context,
|
||||
towerContext,
|
||||
selection,
|
||||
classShortenOption = { classShortenOption(analysisSession.firSymbolBuilder.buildSymbol(it) as KtClassLikeSymbol) },
|
||||
callableShortenOption = { callableShortenOption(analysisSession.firSymbolBuilder.buildSymbol(it) as KtCallableSymbol) },
|
||||
classShortenOption = { classShortenOption(buildSymbol(it) as KtClassLikeSymbol) },
|
||||
callableShortenOption = { callableShortenOption(buildSymbol(it) as KtCallableSymbol) },
|
||||
firResolveSession,
|
||||
)
|
||||
firDeclaration.accept(collector)
|
||||
|
||||
val kDocQualifiersToShorten = collectKDocQualifiersToShorten(
|
||||
file,
|
||||
selection,
|
||||
classShortenOption = { minOf(classShortenOption(it), ShortenOption.SHORTEN_IF_ALREADY_IMPORTED) },
|
||||
callableShortenOption = { minOf(callableShortenOption(it), ShortenOption.SHORTEN_IF_ALREADY_IMPORTED) },
|
||||
)
|
||||
|
||||
return ShortenCommandImpl(
|
||||
file.createSmartPointer(),
|
||||
collector.getNamesToImport(starImport = false).toList(),
|
||||
collector.getNamesToImport(starImport = true).toList(),
|
||||
collector.typesToShorten.map { it.element }.distinct().map { it.createSmartPointer() },
|
||||
collector.qualifiersToShorten.map { it.element }.distinct().map { it.createSmartPointer() }
|
||||
collector.qualifiersToShorten.map { it.element }.distinct().map { it.createSmartPointer() },
|
||||
kDocQualifiersToShorten.map { it.element }.distinct().map { it.createSmartPointer() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun collectKDocQualifiersToShorten(
|
||||
file: KtFile,
|
||||
selection: TextRange,
|
||||
classShortenOption: (KtClassLikeSymbol) -> ShortenOption,
|
||||
callableShortenOption: (KtCallableSymbol) -> ShortenOption,
|
||||
): List<ShortenKDocQualifier> {
|
||||
val kDocQualifiersToShorten = mutableListOf<ShortenKDocQualifier>()
|
||||
val elementToVisit = file.findSmallestElementOfTypeContainingSelection<KtElement>(selection)
|
||||
|
||||
fun addKDocToShorten(kDocName: KDocName) {
|
||||
kDocQualifiersToShorten.add(ShortenKDocQualifier(kDocName))
|
||||
}
|
||||
|
||||
elementToVisit?.accept(object : KtVisitorVoid() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
if (!element.textRange.intersects(selection)) return
|
||||
|
||||
if (!selection.contains(element.textRange) || element !is KDocName) {
|
||||
element.acceptChildren(this)
|
||||
return
|
||||
}
|
||||
|
||||
if (element.getQualifier() == null) return
|
||||
|
||||
val shouldShortenKDocQualifier = shouldShortenKDocQualifier(
|
||||
element,
|
||||
classShortenOption = { classShortenOption(buildSymbol(it) as KtClassLikeSymbol) },
|
||||
callableShortenOption = { callableShortenOption(buildSymbol(it) as KtCallableSymbol) },
|
||||
)
|
||||
if (shouldShortenKDocQualifier) {
|
||||
addKDocToShorten(element)
|
||||
} else {
|
||||
element.acceptChildren(this)
|
||||
|
||||
if (element.getQualifier()?.getNameText() == ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE) {
|
||||
addKDocToShorten(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return kDocQualifiersToShorten
|
||||
}
|
||||
|
||||
private fun shouldShortenKDocQualifier(
|
||||
kDocName: KDocName,
|
||||
classShortenOption: (FirClassLikeSymbol<*>) -> ShortenOption,
|
||||
callableShortenOption: (FirCallableSymbol<*>) -> ShortenOption,
|
||||
): Boolean {
|
||||
val fqName = kDocName.getQualifiedNameAsFqName().dropFakeRootPrefixIfPresent()
|
||||
|
||||
val resolvedSymbols = with(analysisSession) {
|
||||
val shortFqName = FqName.topLevel(fqName.shortName())
|
||||
val owner = kDocName.getContainingDoc().owner
|
||||
|
||||
KDocReferenceResolver.resolveKdocFqName(shortFqName, shortFqName, owner ?: kDocName.containingKtFile)
|
||||
}
|
||||
|
||||
resolvedSymbols.firstIsInstanceOrNull<KtCallableSymbol>()?.firSymbol?.let { availableCallable ->
|
||||
return canShorten(fqName, availableCallable.callableId.asSingleFqName()) { callableShortenOption(availableCallable) }
|
||||
}
|
||||
|
||||
resolvedSymbols.firstIsInstanceOrNull<KtClassLikeSymbol>()?.firSymbol?.let { availableClassifier ->
|
||||
return canShorten(fqName, availableClassifier.classId.asSingleFqName()) { classShortenOption(availableClassifier) }
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun canShorten(fqNameToShorten: FqName, fqNameOfAvailableSymbol: FqName, getShortenOption: () -> ShortenOption): Boolean =
|
||||
fqNameToShorten == fqNameOfAvailableSymbol && getShortenOption() != ShortenOption.DO_NOT_SHORTEN
|
||||
|
||||
private fun buildSymbol(firSymbol: FirBasedSymbol<*>): KtSymbol = analysisSession.firSymbolBuilder.buildSymbol(firSymbol)
|
||||
}
|
||||
|
||||
private fun KtFile.findSmallestDeclarationContainingSelection(selection: TextRange): KtDeclaration? =
|
||||
private fun FqName.dropFakeRootPrefixIfPresent(): FqName {
|
||||
val pathSegments = pathSegments()
|
||||
return if (pathSegments.firstOrNull()?.asString() == ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE) {
|
||||
FqName.fromSegments(pathSegments.drop(1).map { it.asString() })
|
||||
} else this
|
||||
}
|
||||
|
||||
private inline fun <reified T : KtElement> KtFile.findSmallestElementOfTypeContainingSelection(selection: TextRange): T? =
|
||||
findElementAt(selection.startOffset)
|
||||
?.parentsOfType<KtDeclaration>(withSelf = true)
|
||||
?.parentsOfType<T>(withSelf = true)
|
||||
?.firstOrNull { selection in it.textRange }
|
||||
|
||||
/**
|
||||
@@ -301,7 +396,7 @@ private sealed class ElementToShorten {
|
||||
private class ShortenType(
|
||||
val element: KtUserType,
|
||||
override val nameToImport: FqName? = null,
|
||||
override val importAllInParent: Boolean = false
|
||||
override val importAllInParent: Boolean = false,
|
||||
) : ElementToShorten()
|
||||
|
||||
private class ShortenQualifier(
|
||||
@@ -310,6 +405,12 @@ private class ShortenQualifier(
|
||||
override val importAllInParent: Boolean = false
|
||||
) : ElementToShorten()
|
||||
|
||||
private class ShortenKDocQualifier(
|
||||
val element: KDocName,
|
||||
override val nameToImport: FqName? = null,
|
||||
override val importAllInParent: Boolean = false
|
||||
) : ElementToShorten()
|
||||
|
||||
private class ElementsToShortenCollector(
|
||||
private val shorteningContext: FirShorteningContext,
|
||||
private val towerContextProvider: FirTowerContextProvider,
|
||||
@@ -1098,6 +1199,7 @@ private class ElementsToShortenCollector(
|
||||
when (elementInfoToShorten) {
|
||||
is ShortenType -> addElementToShorten(elementInfoToShorten.element, nameToImport, isImportWithStar)
|
||||
is ShortenQualifier -> addElementToShorten(elementInfoToShorten.element, nameToImport, isImportWithStar)
|
||||
is ShortenKDocQualifier -> addElementToShorten(elementInfoToShorten.element, nameToImport, isImportWithStar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1124,10 +1226,11 @@ private class ElementsToShortenCollector(
|
||||
|
||||
private class ShortenCommandImpl(
|
||||
override val targetFile: SmartPsiElementPointer<KtFile>,
|
||||
override val importsToAdd: List<FqName>,
|
||||
override val starImportsToAdd: List<FqName>,
|
||||
override val importsToAdd: Set<FqName>,
|
||||
override val starImportsToAdd: Set<FqName>,
|
||||
override val typesToShorten: List<SmartPsiElementPointer<KtUserType>>,
|
||||
override val qualifiersToShorten: List<SmartPsiElementPointer<KtDotQualifiedExpression>>,
|
||||
override val kDocQualifiersToShorten: List<SmartPsiElementPointer<KDocName>>,
|
||||
) : ShortenCommand
|
||||
|
||||
private fun KtUserType.hasFakeRootPrefix(): Boolean =
|
||||
|
||||
+1
-2
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.importedFromObjectOrStaticData
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.originalForWrappedIntegerOperator
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
|
||||
internal interface KtFirSymbol<out S : FirBasedSymbol<*>> : KtSymbol, KtLifetimeOwner {
|
||||
@@ -106,7 +105,7 @@ internal tailrec fun FirDeclaration.ktSymbolOrigin(): KtSymbolOrigin = when (ori
|
||||
}
|
||||
|
||||
internal fun KtClassLikeSymbol.getSymbolKind(): KtSymbolKind {
|
||||
val firSymbol = firSymbol as FirClassLikeSymbol<*>
|
||||
val firSymbol = firSymbol
|
||||
if (firSymbol.isLocal) {
|
||||
// TODO: hack should be dropped after KT-54390
|
||||
when {
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ internal val KtEnumEntrySymbol.firSymbol: FirEnumEntrySymbol get() = (this as Kt
|
||||
internal val KtConstructorSymbol.firSymbol: FirConstructorSymbol get() = (this as KtFirSymbol<*>).firSymbol as FirConstructorSymbol
|
||||
internal val KtPropertyAccessorSymbol.firSymbol: FirPropertyAccessorSymbol get() = (this as KtFirSymbol<*>).firSymbol as FirPropertyAccessorSymbol
|
||||
internal val KtClassInitializerSymbol.firSymbol: FirAnonymousInitializerSymbol get() = (this as KtFirSymbol<*>).firSymbol as FirAnonymousInitializerSymbol
|
||||
internal val KtClassLikeSymbol.firSymbol: FirClassLikeSymbol<*> get() = (this as KtFirSymbol<*>).firSymbol as FirClassLikeSymbol<*>
|
||||
|
||||
|
||||
fun FirBasedSymbol<*>.getContainingKtModule(firResolveSession: LLFirResolveSession): KtModule {
|
||||
|
||||
+18
@@ -238,6 +238,24 @@ public class FirIdeNormalAnalysisSourceModuleReferenceShortenerTestGenerated ext
|
||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/extensionFromObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kdoc.kt")
|
||||
public void testKdoc() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/kdoc.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kdocQualifierSelected.kt")
|
||||
public void testKdocQualifierSelected() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/kdocQualifierSelected.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kdocUnresolved.kt")
|
||||
public void testKdocUnresolved() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/referenceShortener/referenceShortener/kdocUnresolved.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("multipleImport.kt")
|
||||
public void testMultipleImport() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user