Analysis API: add normal FE10 import optimizer (taken from plugin code)
This commit is contained in:
committed by
Ilya Kirillov
parent
c29482f25f
commit
7fd441f16a
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.descriptors
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isPackageDirectiveExpression
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
|
||||
@Suppress("ClassName")
|
||||
sealed class CallType<TReceiver : KtElement?>(val descriptorKindFilter: DescriptorKindFilter) {
|
||||
object UNKNOWN : CallType<Nothing?>(DescriptorKindFilter.ALL)
|
||||
|
||||
object DEFAULT : CallType<Nothing?>(DescriptorKindFilter.ALL)
|
||||
|
||||
object DOT : CallType<KtExpression>(DescriptorKindFilter.ALL)
|
||||
|
||||
object SAFE : CallType<KtExpression>(DescriptorKindFilter.ALL)
|
||||
|
||||
object SUPER_MEMBERS : CallType<KtSuperExpression>(
|
||||
DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions exclude AbstractMembersExclude
|
||||
)
|
||||
|
||||
object INFIX : CallType<KtExpression>(DescriptorKindFilter.FUNCTIONS exclude NonInfixExclude)
|
||||
|
||||
object OPERATOR : CallType<KtExpression>(DescriptorKindFilter.FUNCTIONS exclude NonOperatorExclude)
|
||||
|
||||
object CALLABLE_REFERENCE : CallType<KtExpression?>(DescriptorKindFilter.CALLABLES exclude CallableReferenceExclude)
|
||||
|
||||
object IMPORT_DIRECTIVE : CallType<KtExpression?>(DescriptorKindFilter.ALL)
|
||||
|
||||
object PACKAGE_DIRECTIVE : CallType<KtExpression?>(DescriptorKindFilter.PACKAGES)
|
||||
|
||||
object TYPE : CallType<KtExpression?>(
|
||||
DescriptorKindFilter(DescriptorKindFilter.CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK)
|
||||
exclude DescriptorKindExclude.EnumEntry
|
||||
)
|
||||
|
||||
object DELEGATE : CallType<KtExpression?>(DescriptorKindFilter.FUNCTIONS exclude NonOperatorExclude)
|
||||
|
||||
object ANNOTATION : CallType<KtExpression?>(
|
||||
DescriptorKindFilter(DescriptorKindFilter.CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK)
|
||||
exclude NonAnnotationClassifierExclude
|
||||
)
|
||||
|
||||
private object NonInfixExclude : DescriptorKindExclude() {
|
||||
override fun excludes(descriptor: DeclarationDescriptor) =
|
||||
!(descriptor is SimpleFunctionDescriptor && descriptor.isInfix)
|
||||
|
||||
override val fullyExcludedDescriptorKinds: Int
|
||||
get() = 0
|
||||
}
|
||||
|
||||
private object NonOperatorExclude : DescriptorKindExclude() {
|
||||
override fun excludes(descriptor: DeclarationDescriptor) =
|
||||
!(descriptor is SimpleFunctionDescriptor && descriptor.isOperator)
|
||||
|
||||
override val fullyExcludedDescriptorKinds: Int
|
||||
get() = 0
|
||||
}
|
||||
|
||||
private object CallableReferenceExclude : DescriptorKindExclude() {
|
||||
override fun excludes(descriptor: DeclarationDescriptor) /* currently not supported for locals and synthetic */ =
|
||||
descriptor !is CallableMemberDescriptor || descriptor.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
|
||||
|
||||
override val fullyExcludedDescriptorKinds: Int
|
||||
get() = 0
|
||||
}
|
||||
|
||||
private object NonAnnotationClassifierExclude : DescriptorKindExclude() {
|
||||
override fun excludes(descriptor: DeclarationDescriptor): Boolean {
|
||||
if (descriptor !is ClassifierDescriptor) return false
|
||||
return descriptor !is ClassDescriptor || descriptor.kind != ClassKind.ANNOTATION_CLASS
|
||||
}
|
||||
|
||||
override val fullyExcludedDescriptorKinds: Int get() = 0
|
||||
}
|
||||
|
||||
private object AbstractMembersExclude : DescriptorKindExclude() {
|
||||
override fun excludes(descriptor: DeclarationDescriptor) =
|
||||
descriptor is CallableMemberDescriptor && descriptor.modality == Modality.ABSTRACT
|
||||
|
||||
override val fullyExcludedDescriptorKinds: Int
|
||||
get() = 0
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ClassName")
|
||||
sealed class CallTypeAndReceiver<TReceiver : KtElement?, out TCallType : CallType<TReceiver>>(
|
||||
val callType: TCallType,
|
||||
val receiver: TReceiver
|
||||
) {
|
||||
object UNKNOWN : CallTypeAndReceiver<Nothing?, CallType.UNKNOWN>(CallType.UNKNOWN, null)
|
||||
object DEFAULT : CallTypeAndReceiver<Nothing?, CallType.DEFAULT>(CallType.DEFAULT, null)
|
||||
class DOT(receiver: KtExpression) : CallTypeAndReceiver<KtExpression, CallType.DOT>(CallType.DOT, receiver)
|
||||
class SAFE(receiver: KtExpression) : CallTypeAndReceiver<KtExpression, CallType.SAFE>(CallType.SAFE, receiver)
|
||||
class SUPER_MEMBERS(receiver: KtSuperExpression) : CallTypeAndReceiver<KtSuperExpression, CallType.SUPER_MEMBERS>(
|
||||
CallType.SUPER_MEMBERS, receiver
|
||||
)
|
||||
|
||||
class INFIX(receiver: KtExpression) : CallTypeAndReceiver<KtExpression, CallType.INFIX>(CallType.INFIX, receiver)
|
||||
class OPERATOR(receiver: KtExpression) : CallTypeAndReceiver<KtExpression, CallType.OPERATOR>(CallType.OPERATOR, receiver)
|
||||
class CALLABLE_REFERENCE(receiver: KtExpression?) : CallTypeAndReceiver<KtExpression?, CallType.CALLABLE_REFERENCE>(
|
||||
CallType.CALLABLE_REFERENCE, receiver
|
||||
)
|
||||
|
||||
class IMPORT_DIRECTIVE(receiver: KtExpression?) : CallTypeAndReceiver<KtExpression?, CallType.IMPORT_DIRECTIVE>(
|
||||
CallType.IMPORT_DIRECTIVE, receiver
|
||||
)
|
||||
|
||||
class PACKAGE_DIRECTIVE(receiver: KtExpression?) :
|
||||
CallTypeAndReceiver<KtExpression?, CallType.PACKAGE_DIRECTIVE>(CallType.PACKAGE_DIRECTIVE, receiver)
|
||||
|
||||
class TYPE(receiver: KtExpression?) : CallTypeAndReceiver<KtExpression?, CallType.TYPE>(CallType.TYPE, receiver)
|
||||
class DELEGATE(receiver: KtExpression?) : CallTypeAndReceiver<KtExpression?, CallType.DELEGATE>(CallType.DELEGATE, receiver)
|
||||
class ANNOTATION(receiver: KtExpression?) : CallTypeAndReceiver<KtExpression?, CallType.ANNOTATION>(CallType.ANNOTATION, receiver)
|
||||
|
||||
companion object {
|
||||
fun detect(expression: KtSimpleNameExpression): CallTypeAndReceiver<*, *> {
|
||||
val parent = expression.parent
|
||||
if (parent is KtCallableReferenceExpression && expression == parent.callableReference) {
|
||||
return CALLABLE_REFERENCE(parent.receiverExpression)
|
||||
}
|
||||
|
||||
val receiverExpression = expression.getReceiverExpression()
|
||||
|
||||
if (expression.isImportDirectiveExpression()) {
|
||||
return IMPORT_DIRECTIVE(receiverExpression)
|
||||
}
|
||||
|
||||
if (expression.isPackageDirectiveExpression()) {
|
||||
return PACKAGE_DIRECTIVE(receiverExpression)
|
||||
}
|
||||
|
||||
if (parent is KtUserType) {
|
||||
val constructorCallee = (parent.parent as? KtTypeReference)?.parent as? KtConstructorCalleeExpression
|
||||
if (constructorCallee != null && constructorCallee.parent is KtAnnotationEntry) {
|
||||
return ANNOTATION(receiverExpression)
|
||||
}
|
||||
|
||||
return TYPE(receiverExpression)
|
||||
}
|
||||
|
||||
when (expression) {
|
||||
is KtOperationReferenceExpression -> {
|
||||
if (receiverExpression == null) {
|
||||
return UNKNOWN // incomplete code
|
||||
}
|
||||
return when (parent) {
|
||||
is KtBinaryExpression -> {
|
||||
if (parent.operationToken == KtTokens.IDENTIFIER)
|
||||
INFIX(receiverExpression)
|
||||
else
|
||||
OPERATOR(receiverExpression)
|
||||
}
|
||||
|
||||
is KtUnaryExpression -> OPERATOR(receiverExpression)
|
||||
|
||||
else -> error("Unknown parent for JetOperationReferenceExpression: $parent with text '${parent.text}'")
|
||||
}
|
||||
}
|
||||
|
||||
is KtNameReferenceExpression -> {
|
||||
if (receiverExpression == null) {
|
||||
return DEFAULT
|
||||
}
|
||||
|
||||
if (receiverExpression is KtSuperExpression) {
|
||||
return SUPER_MEMBERS(receiverExpression)
|
||||
}
|
||||
|
||||
return when (parent) {
|
||||
is KtCallExpression -> {
|
||||
if ((parent.parent as KtQualifiedExpression).operationSign == KtTokens.SAFE_ACCESS)
|
||||
SAFE(receiverExpression)
|
||||
else
|
||||
DOT(receiverExpression)
|
||||
}
|
||||
|
||||
is KtQualifiedExpression -> {
|
||||
if (parent.operationSign == KtTokens.SAFE_ACCESS)
|
||||
SAFE(receiverExpression)
|
||||
else
|
||||
DOT(receiverExpression)
|
||||
}
|
||||
|
||||
else -> error("Unknown parent for JetNameReferenceExpression with receiver: $parent")
|
||||
}
|
||||
}
|
||||
|
||||
else -> return UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+310
-11
@@ -5,15 +5,42 @@
|
||||
|
||||
package org.jetbrains.kotlin.analysis.api.descriptors.components
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizer
|
||||
import org.jetbrains.kotlin.analysis.api.components.KtImportOptimizerResult
|
||||
import org.jetbrains.kotlin.analysis.api.descriptors.CallTypeAndReceiver
|
||||
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisFacade
|
||||
import org.jetbrains.kotlin.analysis.api.descriptors.KtFe10AnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.descriptors.components.base.Fe10KtAnalysisSessionComponent
|
||||
import org.jetbrains.kotlin.analysis.api.descriptors.symbols.psiBased.base.getResolutionScope
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
|
||||
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.references.KDocReference
|
||||
import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtImportDirective
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
|
||||
import org.jetbrains.kotlin.references.fe10.Fe10SyntheticPropertyAccessorReference
|
||||
import org.jetbrains.kotlin.references.fe10.KtFe10InvokeFunctionReference
|
||||
import org.jetbrains.kotlin.references.fe10.base.KtFe10Reference
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.*
|
||||
import org.jetbrains.kotlin.types.error.ErrorFunctionDescriptor
|
||||
|
||||
internal class KtFe10ImportOptimizer(
|
||||
override val analysisSession: KtFe10AnalysisSession
|
||||
@@ -22,25 +49,297 @@ internal class KtFe10ImportOptimizer(
|
||||
get() = analysisSession.token
|
||||
|
||||
override fun analyseImports(file: KtFile): KtImportOptimizerResult = withValidityAssertion {
|
||||
val (allUnderImports, otherImports) = file.importDirectives.partition { it.isAllUnder }
|
||||
val imports = file.importDirectives
|
||||
|
||||
val unusedImports = LinkedHashSet<KtImportDirective>()
|
||||
val importedPackages = HashSet<FqName>()
|
||||
val unusedImports = mutableSetOf<KtImportDirective>()
|
||||
val visitor = CollectUsedDescriptorsVisitor(file)
|
||||
file.accept(visitor)
|
||||
val optimizerData = visitor.data
|
||||
|
||||
for (import in allUnderImports) {
|
||||
val explicitlyImportedFqNames = mutableSetOf<FqName>()
|
||||
|
||||
for (import in imports) {
|
||||
val fqName = import.importedFqName ?: continue
|
||||
if (!importedPackages.add(fqName)) {
|
||||
unusedImports += import
|
||||
if (import.alias == null) {
|
||||
explicitlyImportedFqNames += fqName
|
||||
}
|
||||
}
|
||||
|
||||
for (import in otherImports) {
|
||||
val fqName = import.importedFqName ?: continue
|
||||
if (import.alias == null && fqName.parent() in importedPackages) {
|
||||
val parentFqNames = mutableSetOf<FqName>()
|
||||
val importPaths = HashSet<ImportPath>(file.importDirectives.size)
|
||||
val fqNames = optimizerData.namesToImport
|
||||
|
||||
for ((_, fqName) in optimizerData.descriptorsToImport) {
|
||||
// we don't add parents of explicitly imported fq-names because such imports are not needed
|
||||
if (fqName in explicitlyImportedFqNames) continue
|
||||
val parentFqName = fqName.parent()
|
||||
if (!parentFqName.isRoot) {
|
||||
parentFqNames.add(parentFqName)
|
||||
}
|
||||
}
|
||||
|
||||
val invokeFunctionCallFqNames = optimizerData.references.flatMap { reference ->
|
||||
val element = reference.element
|
||||
val context = analyze(element) {
|
||||
(this as KtFe10AnalysisSession).analysisContext.analyze(element, Fe10AnalysisFacade.AnalysisMode.PARTIAL)
|
||||
}
|
||||
val mainReference = (element as? KtCallExpression)?.mainReference as? KtFe10InvokeFunctionReference
|
||||
mainReference?.getTargetDescriptors(context)?.map { it.fqNameSafe }.orEmpty()
|
||||
}
|
||||
|
||||
for (import in imports) {
|
||||
val importPath = import.importPath ?: continue
|
||||
|
||||
val isUsed = when {
|
||||
importPath.importedName in optimizerData.unresolvedNames -> true
|
||||
!importPaths.add(importPath) -> false
|
||||
importPath.isAllUnder -> optimizerData.unresolvedNames.isNotEmpty() || importPath.fqName in parentFqNames
|
||||
importPath.fqName in fqNames -> importPath.importedName?.let { it in fqNames.getValue(importPath.fqName) } ?: false
|
||||
importPath.fqName in invokeFunctionCallFqNames -> true
|
||||
// case for type alias
|
||||
else -> import.targetDescriptors().firstOrNull()?.let { it.fqNameSafe in fqNames } ?: false
|
||||
}
|
||||
|
||||
if (!isUsed) {
|
||||
unusedImports += import
|
||||
}
|
||||
}
|
||||
|
||||
return KtImportOptimizerResult(unusedImports)
|
||||
}
|
||||
|
||||
private data class ImportableDescriptor(
|
||||
val descriptor: DeclarationDescriptor,
|
||||
val fqName: FqName,
|
||||
)
|
||||
|
||||
private interface AbstractReference {
|
||||
val element: KtElement
|
||||
val dependsOnNames: Collection<Name>
|
||||
}
|
||||
|
||||
private class InputData(
|
||||
val descriptorsToImport: Set<ImportableDescriptor>,
|
||||
val namesToImport: Map<FqName, Set<Name>>,
|
||||
val references: Collection<AbstractReference>,
|
||||
val unresolvedNames: Set<Name>,
|
||||
)
|
||||
|
||||
private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() {
|
||||
private val currentPackageName = file.packageFqName
|
||||
private val aliases: Map<FqName, List<Name>> = file.importDirectives
|
||||
.asSequence()
|
||||
.filter { !it.isAllUnder && it.alias != null }
|
||||
.mapNotNull { it.importPath }
|
||||
.groupBy(keySelector = { it.fqName }, valueTransform = { it.importedName as Name })
|
||||
|
||||
private val descriptorsToImport = hashSetOf<ImportableDescriptor>()
|
||||
private val namesToImport = hashMapOf<FqName, MutableSet<Name>>()
|
||||
private val abstractRefs = ArrayList<AbstractReference>()
|
||||
private val unresolvedNames = hashSetOf<Name>()
|
||||
|
||||
val data get() = InputData(descriptorsToImport, namesToImport, abstractRefs, unresolvedNames)
|
||||
|
||||
override fun visitElement(element: PsiElement) {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitImportList(importList: KtImportList) {
|
||||
}
|
||||
|
||||
override fun visitPackageDirective(directive: KtPackageDirective) {
|
||||
}
|
||||
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
super.visitKtElement(element)
|
||||
if (element is KtLabelReferenceExpression) return
|
||||
|
||||
val references = element.references.ifEmpty { return }
|
||||
val bindingContext = analyze(element) {
|
||||
(this as KtFe10AnalysisSession).analysisContext.analyze(element, Fe10AnalysisFacade.AnalysisMode.PARTIAL)
|
||||
}
|
||||
val isResolved = hasResolvedDescriptor(element, bindingContext)
|
||||
|
||||
for (reference in references) {
|
||||
if (reference !is KtReference) continue
|
||||
|
||||
abstractRefs.add(AbstractReferenceImpl(reference))
|
||||
|
||||
val names = reference.resolvesByNames.toSet()
|
||||
if (!isResolved) {
|
||||
unresolvedNames += names
|
||||
}
|
||||
|
||||
for (target in reference.targets(bindingContext)) {
|
||||
val importableDescriptor = target.getImportableDescriptor()
|
||||
val importableFqName = target.importableFqName ?: continue
|
||||
val parentFqName = importableFqName.parent()
|
||||
if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages
|
||||
|
||||
if (target !is PackageViewDescriptor && parentFqName == currentPackageName && (importableFqName !in aliases)) continue
|
||||
|
||||
if (!reference.canBeResolvedViaImport(target, bindingContext)) continue
|
||||
|
||||
if (importableDescriptor.name in names && isAccessibleAsMember(importableDescriptor, element, bindingContext)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val descriptorNames = (aliases[importableFqName].orEmpty() + importableFqName.shortName()).intersect(names)
|
||||
namesToImport.getOrPut(importableFqName) { hashSetOf() } += descriptorNames
|
||||
descriptorsToImport += ImportableDescriptor(importableDescriptor, importableFqName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAccessibleAsMember(target: DeclarationDescriptor, place: KtElement, bindingContext: BindingContext): Boolean {
|
||||
if (target.containingDeclaration !is ClassDescriptor) return false
|
||||
|
||||
fun isInScope(scope: HierarchicalScope): Boolean {
|
||||
return when (target) {
|
||||
is FunctionDescriptor ->
|
||||
scope.findFunction(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
|
||||
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
|
||||
|
||||
is PropertyDescriptor ->
|
||||
scope.findVariable(target.name, NoLookupLocation.FROM_IDE) { it == target } != null
|
||||
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
|
||||
|
||||
is ClassDescriptor ->
|
||||
scope.findClassifier(target.name, NoLookupLocation.FROM_IDE) == target
|
||||
&& bindingContext[BindingContext.DEPRECATED_SHORT_NAME_ACCESS, place] != true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val resolutionScope = place.getResolutionScope(bindingContext) ?: return false
|
||||
val noImportsScope = resolutionScope.replaceImportingScopes(null)
|
||||
|
||||
if (isInScope(noImportsScope)) return true
|
||||
// classes not accessible through receivers, only their constructors
|
||||
return if (target is ClassDescriptor) false
|
||||
else resolutionScope.getImplicitReceiversHierarchy().any { isInScope(it.type.memberScope.memberScopeAsImportingScope()) }
|
||||
}
|
||||
|
||||
private class AbstractReferenceImpl(private val reference: KtReference) : AbstractReference {
|
||||
override val element: KtElement
|
||||
get() = reference.element
|
||||
|
||||
override val dependsOnNames: Collection<Name>
|
||||
get() {
|
||||
val resolvesByNames = reference.resolvesByNames
|
||||
if (reference is KtInvokeFunctionReference) {
|
||||
val additionalNames =
|
||||
(reference.element.calleeExpression as? KtNameReferenceExpression)?.mainReference?.resolvesByNames
|
||||
|
||||
if (additionalNames != null) {
|
||||
return resolvesByNames + additionalNames
|
||||
}
|
||||
}
|
||||
|
||||
return resolvesByNames
|
||||
}
|
||||
|
||||
override fun toString() = when (reference) {
|
||||
is Fe10SyntheticPropertyAccessorReference -> {
|
||||
reference.toString().replace(
|
||||
"Fe10SyntheticPropertyAccessorReference",
|
||||
if (reference.getter) "Getter" else "Setter"
|
||||
)
|
||||
}
|
||||
|
||||
else -> reference.toString().replace("Fe10", "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtReference.targets(bindingContext: BindingContext): Collection<DeclarationDescriptor> {
|
||||
//class qualifiers that refer to companion objects should be considered (containing) class references
|
||||
return bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) }
|
||||
?: (this as? KtFe10Reference)?.resolveToDescriptors(bindingContext).orEmpty()
|
||||
}
|
||||
|
||||
private fun hasResolvedDescriptor(element: KtElement, bindingContext: BindingContext): Boolean {
|
||||
return if (element is KtCallElement)
|
||||
element.getResolvedCall(bindingContext) != null
|
||||
else
|
||||
(element.mainReference as? KtFe10Reference)?.resolveToDescriptors(bindingContext)?.let { descriptors ->
|
||||
descriptors.isNotEmpty() && descriptors.none { it is ErrorFunctionDescriptor }
|
||||
} == true
|
||||
}
|
||||
|
||||
private val DeclarationDescriptor.importableFqName: FqName?
|
||||
get() {
|
||||
if (!canBeReferencedViaImport()) return null
|
||||
return getImportableDescriptor().fqNameSafe
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean {
|
||||
if (this is PackageViewDescriptor ||
|
||||
DescriptorUtils.isTopLevelDeclaration(this) ||
|
||||
this is CallableDescriptor && DescriptorUtils.isStaticDeclaration(this)
|
||||
) {
|
||||
return !name.isSpecial
|
||||
}
|
||||
|
||||
//Both TypeAliasDescriptor and ClassDescriptor
|
||||
val parentClassifier = containingDeclaration as? ClassifierDescriptorWithTypeParameters ?: return false
|
||||
if (!parentClassifier.canBeReferencedViaImport()) return false
|
||||
|
||||
return when (this) {
|
||||
is ConstructorDescriptor -> !parentClassifier.isInner // inner class constructors can't be referenced via import
|
||||
is ClassDescriptor, is TypeAliasDescriptor -> true
|
||||
else -> parentClassifier is ClassDescriptor && parentClassifier.kind == ClassKind.OBJECT
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtReference.canBeResolvedViaImport(target: DeclarationDescriptor, bindingContext: BindingContext): Boolean {
|
||||
if (this is KDocReference) {
|
||||
val qualifier = element.getQualifier() ?: return true
|
||||
return if (target.isExtension) {
|
||||
val elementHasFunctionDescriptor =
|
||||
element.resolveMainReferenceToDescriptors(bindingContext).any { it is FunctionDescriptor }
|
||||
val qualifierHasClassDescriptor =
|
||||
qualifier.resolveMainReferenceToDescriptors(bindingContext).any { it is ClassDescriptor }
|
||||
elementHasFunctionDescriptor && qualifierHasClassDescriptor
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return element.canBeResolvedViaImport(target, bindingContext)
|
||||
}
|
||||
|
||||
fun KtElement.resolveMainReferenceToDescriptors(bindingContext: BindingContext): Collection<DeclarationDescriptor> {
|
||||
return (mainReference as? KtFe10Reference)?.resolveToDescriptors(bindingContext).orEmpty()
|
||||
}
|
||||
|
||||
|
||||
private fun KtElement.canBeResolvedViaImport(target: DeclarationDescriptor, bindingContext: BindingContext): Boolean {
|
||||
if (!target.canBeReferencedViaImport()) return false
|
||||
if (target.isExtension) return true // assume that any type of reference can use imports when resolved to extension
|
||||
if (this !is KtNameReferenceExpression) return false
|
||||
|
||||
val callTypeAndReceiver = CallTypeAndReceiver.detect(this)
|
||||
if (callTypeAndReceiver.receiver != null) {
|
||||
if (target !is PropertyDescriptor || !target.type.isExtensionFunctionType) return false
|
||||
if (callTypeAndReceiver !is CallTypeAndReceiver.DOT && callTypeAndReceiver !is CallTypeAndReceiver.SAFE) return false
|
||||
|
||||
val resolvedCall = bindingContext[BindingContext.CALL, this].getResolvedCall(bindingContext)
|
||||
as? VariableAsFunctionResolvedCall ?: return false
|
||||
if (resolvedCall.variableCall.explicitReceiverKind.isDispatchReceiver) return false
|
||||
}
|
||||
|
||||
if (parent is KtThisExpression || parent is KtSuperExpression) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtImportDirective.targetDescriptors(): Collection<DeclarationDescriptor> {
|
||||
// For codeFragments imports are created in dummy file
|
||||
//if (this.containingKtFile.doNotAnalyze != null) return emptyList()
|
||||
val nameExpression = importedReference?.getQualifiedElementSelector() as? KtSimpleNameExpression ?: return emptyList()
|
||||
val bindingContext = analyze(nameExpression) {
|
||||
(this as KtFe10AnalysisSession).analysisContext.analyze(nameExpression, Fe10AnalysisFacade.AnalysisMode.FULL)
|
||||
}
|
||||
return (nameExpression.mainReference as? KtFe10Reference)?.resolveToDescriptors(bindingContext).orEmpty()
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.test.cases.generated.cases.components.importOptimizer;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.analysis.api.fe10.test.configurator.AnalysisApiFe10TestConfiguratorFactory;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfiguratorFactoryData;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisSessionMode;
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiMode;
|
||||
import org.jetbrains.kotlin.analysis.api.impl.base.test.cases.components.importOptimizer.AbstractAnalysisApiImportOptimizerTest;
|
||||
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/importOptimizer/analyseImports")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
public class Fe10IdeNormalAnalysisSourceModuleAnalysisApiImportOptimizerTestGenerated extends AbstractAnalysisApiImportOptimizerTest {
|
||||
@NotNull
|
||||
@Override
|
||||
public AnalysisApiTestConfigurator getConfigurator() {
|
||||
return AnalysisApiFe10TestConfiguratorFactory.INSTANCE.createConfigurator(
|
||||
new AnalysisApiTestConfiguratorFactoryData(
|
||||
FrontendKind.Fe10,
|
||||
TestModuleKind.Source,
|
||||
AnalysisSessionMode.Normal,
|
||||
AnalysisApiMode.Ide
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllFilesPresentInAnalyseImports() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/importOptimizer/analyseImports"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedAliasedTypeImport.kt")
|
||||
public void testUnusedAliasedTypeImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedAliasedTypeImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedFunctionImports.kt")
|
||||
public void testUnusedFunctionImports() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedFunctionImports.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("unusedInvokeOperatorImport.kt")
|
||||
public void testUnusedInvokeOperatorImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/unusedInvokeOperatorImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedAliasedFunctionReference.kt")
|
||||
public void testUsedAliasedFunctionReference() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedAliasedFunctionReference.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedAliasedTypeImport.kt")
|
||||
public void testUsedAliasedTypeImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedAliasedTypeImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedFunctionImport.kt")
|
||||
public void testUsedFunctionImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedFunctionImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedInvokeOperatorAliasedImport.kt")
|
||||
public void testUsedInvokeOperatorAliasedImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedInvokeOperatorAliasedImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedInvokeOperatorExplicitImport.kt")
|
||||
public void testUsedInvokeOperatorExplicitImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedInvokeOperatorExplicitImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedInvokeOperatorImport.kt")
|
||||
public void testUsedInvokeOperatorImport() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedInvokeOperatorImport.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedTypeAsTypeParameter.kt")
|
||||
public void testUsedTypeAsTypeParameter() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/usedTypeAsTypeParameter.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
public class ReferencesWithErrors {
|
||||
@Test
|
||||
public void testAllFilesPresentInReferencesWithErrors() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedConstructor_invalidArguments.kt")
|
||||
public void testUsedConstructor_invalidArguments() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedConstructor_invalidArguments.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedConstructor_missingCall.kt")
|
||||
public void testUsedConstructor_missingCall() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedConstructor_missingCall.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedExtensionFunction_invalidArguments.kt")
|
||||
public void testUsedExtensionFunction_invalidArguments() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedExtensionFunction_invalidArguments.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedExtensionProperty_invalidReceiver.kt")
|
||||
public void testUsedExtensionProperty_invalidReceiver() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedExtensionProperty_invalidReceiver.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedInvokeOperator_invalidArguments.kt")
|
||||
public void testUsedInvokeOperator_invalidArguments() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedInvokeOperator_invalidArguments.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedTypeAsTypeParameter_missingOuterType.kt")
|
||||
public void testUsedTypeAsTypeParameter_missingOuterType() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedTypeAsTypeParameter_missingOuterType.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("usedTypeImport_missingGeneric.kt")
|
||||
public void testUsedTypeImport_missingGeneric() throws Exception {
|
||||
runTest("analysis/analysis-api/testData/components/importOptimizer/analyseImports/referencesWithErrors/usedTypeImport_missingGeneric.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -14,7 +14,10 @@ import org.jetbrains.kotlin.test.services.assertions
|
||||
abstract class AbstractAnalysisApiImportOptimizerTest : AbstractAnalysisApiBasedSingleModuleTest(){
|
||||
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
|
||||
val mainKtFile = ktFiles.singleOrNull() ?: ktFiles.first { it.name == "main.kt" }
|
||||
val unusedImports = analyseForTest(mainKtFile) { analyseImports(mainKtFile).unusedImports }
|
||||
val unusedImports = analyseForTest(mainKtFile) {
|
||||
val results = analyseImports(mainKtFile)
|
||||
results.unusedImports
|
||||
}
|
||||
|
||||
val unusedImportPaths = unusedImports
|
||||
.map { it.importPath ?: error("Import $it should have an import path, instead was ${it.text}") }
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ private fun AnalysisApiTestGroup.generateAnalysisApiComponentsTests() {
|
||||
component("importOptimizer") {
|
||||
test(
|
||||
AbstractAnalysisApiImportOptimizerTest::class,
|
||||
filter = frontendIs(FrontendKind.Fir) and analysisSessionModeIs(AnalysisSessionMode.Normal),
|
||||
filter = analysisSessionModeIs(AnalysisSessionMode.Normal),
|
||||
) {
|
||||
model("analyseImports", pattern = TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user