FIR IDE: Add ImportAllMembersIntention

This commit is contained in:
Tianyu Geng
2021-05-27 15:26:27 -07:00
committed by TeamCityServer
parent 8d69f32d04
commit 66d44f2471
8 changed files with 224 additions and 1 deletions
@@ -1028,7 +1028,6 @@ fun main(args: Array<String>) {
model("checker/diagnosticsMessage", excludedPattern = excludedFirTestdataPattern)
}
testClass<AbstractHighLevelQuickFixTest> {
val pattern = "^([\\w\\-_]+)\\.kt$"
model("quickfix/abstract", pattern = pattern, filenameStartsLowerCase = true)
@@ -1067,6 +1066,7 @@ fun main(args: Array<String>) {
val pattern = "^([\\w\\-_]+)\\.(kt|kts)$"
model("intentions/addPropertyAccessors", pattern = pattern)
model("intentions/specifyTypeExplicitly", pattern = pattern)
model("intentions/importAllMembers", pattern = pattern)
}
testClass<AbstractFirShortenRefsTest> {
@@ -0,0 +1,116 @@
/*
* 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.idea.fir.intentions
import com.intellij.codeInsight.intention.HighPriorityAction
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput
import org.jetbrains.kotlin.idea.api.applicator.applicator
import org.jetbrains.kotlin.idea.fir.api.AbstractHLIntention
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInputProvider
import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider
import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.calls.getSuccessCallSymbolOrNull
import org.jetbrains.kotlin.idea.frontend.api.components.ShortenCommand
import org.jetbrains.kotlin.idea.frontend.api.components.ShortenOption
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.psi.psiUtil.isInImportDirective
class HLImportAllMembersIntention :
AbstractHLIntention<KtExpression, HLImportAllMembersIntention.Input>(KtExpression::class, Companion.applicator), HighPriorityAction {
override val applicabilityRange: HLApplicabilityRange<KtExpression> get() = ApplicabilityRanges.SELF
override val inputProvider: HLApplicatorInputProvider<KtExpression, Input> = inputProvider { psi ->
val target = psi.actualReference?.resolveToSymbol() as? KtNamedClassOrObjectSymbol ?: return@inputProvider null
val classId = target.classIdIfNonLocal ?: return@inputProvider null
if (target.origin != KtSymbolOrigin.JAVA &&
(target.classKind == KtClassKind.OBJECT ||
// One cannot use on-demand import for properties or functions declared inside objects
isReferenceToObjectMemberOrUnresolved(psi))
) {
// Import all members of an object is not supported by Kotlin.
return@inputProvider null
}
val shortenCommand = collectPossibleReferenceShortenings(
psi.containingKtFile,
classShortenOption = {
if (it.classIdIfNonLocal?.isNestedClassIn(classId) == true) {
ShortenOption.SHORTEN_AND_STAR_IMPORT
} else {
ShortenOption.DO_NOT_SHORTEN
}
},
callableShortenOption = {
val containingClassId = if (it is KtConstructorSymbol) {
it.containingClassIdIfNonLocal?.outerClassId
} else {
it.callableIdIfNonLocal?.classId
}
if (containingClassId == classId) {
ShortenOption.SHORTEN_AND_STAR_IMPORT
} else {
ShortenOption.DO_NOT_SHORTEN
}
}
)
if (shortenCommand.isEmpty) return@inputProvider null
Input(classId.asSingleFqName(), shortenCommand)
}
private fun ClassId.isNestedClassIn(classId: ClassId) =
packageFqName == classId.packageFqName && relativeClassName.parent() == classId.relativeClassName
class Input(val fqName: FqName, val shortenCommand: ShortenCommand) : HLApplicatorInput
companion object {
val applicator = applicator<KtExpression, Input> {
familyName(KotlinBundle.lazyMessage("import.members.with"))
actionName { _, input -> KotlinBundle.message("import.members.from.0", input.fqName.asString()) }
isApplicableByPsi { it.isOnTheLeftOfQualificationDot && !it.isInImportDirective() }
applyTo { _, input ->
input.shortenCommand.invokeShortening()
}
}
private val KtExpression.isOnTheLeftOfQualificationDot: Boolean
get() {
return when (val parent = parent) {
is KtDotQualifiedExpression -> this == parent.receiverExpression
is KtUserType -> {
val grandParent = parent.parent as? KtUserType ?: return false
grandParent.qualifier == parent && parent.referenceExpression == this
}
else -> false
}
}
private val KtExpression.actualReference: KtReference?
get() = when (this) {
is KtDotQualifiedExpression -> selectorExpression?.mainReference ?: mainReference
else -> mainReference
}
private fun KtAnalysisSession.isReferenceToObjectMemberOrUnresolved(qualifiedAccess: KtExpression): Boolean {
val selectorExpression: KtExpression? = qualifiedAccess.getQualifiedExpressionForReceiver()?.selectorExpression
val referencedSymbol = when (selectorExpression) {
is KtCallExpression -> selectorExpression.resolveCall()?.targetFunction?.getSuccessCallSymbolOrNull()
is KtNameReferenceExpression -> selectorExpression.mainReference.resolveToSymbol()
else -> return false
} as? KtSymbolWithKind ?: return true
if (referencedSymbol is KtConstructorSymbol) return false
return (referencedSymbol.getContainingSymbol() as? KtClassOrObjectSymbol)?.classKind?.isObject ?: true
}
}
}
@@ -473,4 +473,87 @@ public class HLIntentionTestGenerated extends AbstractHLIntentionTest {
runTest("idea/testData/intentions/specifyTypeExplicitly/unitType.kt");
}
}
@TestMetadata("idea/testData/intentions/importAllMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ImportAllMembers extends AbstractHLIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInImportAllMembers() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/importAllMembers"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("AlreadyImported.kt")
public void testAlreadyImported() throws Exception {
runTest("idea/testData/intentions/importAllMembers/AlreadyImported.kt");
}
@TestMetadata("AlreadyImportedWithStar.kt")
public void testAlreadyImportedWithStar() throws Exception {
runTest("idea/testData/intentions/importAllMembers/AlreadyImportedWithStar.kt");
}
@TestMetadata("AmbiguousCalls.kt")
public void testAmbiguousCalls() throws Exception {
runTest("idea/testData/intentions/importAllMembers/AmbiguousCalls.kt");
}
@TestMetadata("EnumMembers.kt")
public void testEnumMembers() throws Exception {
runTest("idea/testData/intentions/importAllMembers/EnumMembers.kt");
}
@TestMetadata("ImportAllMembersInImport.kt")
public void testImportAllMembersInImport() throws Exception {
runTest("idea/testData/intentions/importAllMembers/ImportAllMembersInImport.kt");
}
@TestMetadata("NotFromCompanionObject.kt")
public void testNotFromCompanionObject() throws Exception {
runTest("idea/testData/intentions/importAllMembers/NotFromCompanionObject.kt");
}
@TestMetadata("NotFromObject.kt")
public void testNotFromObject() throws Exception {
runTest("idea/testData/intentions/importAllMembers/NotFromObject.kt");
}
@TestMetadata("QualifiedName.kt")
public void testQualifiedName() throws Exception {
runTest("idea/testData/intentions/importAllMembers/QualifiedName.kt");
}
@TestMetadata("QualifiedName2.kt")
public void testQualifiedName2() throws Exception {
runTest("idea/testData/intentions/importAllMembers/QualifiedName2.kt");
}
@TestMetadata("RemoveSingleImports.kt")
public void testRemoveSingleImports() throws Exception {
runTest("idea/testData/intentions/importAllMembers/RemoveSingleImports.kt");
}
@TestMetadata("StaticJavaMembers.kt")
public void testStaticJavaMembers() throws Exception {
runTest("idea/testData/intentions/importAllMembers/StaticJavaMembers.kt");
}
@TestMetadata("TypeReference.kt")
public void testTypeReference() throws Exception {
runTest("idea/testData/intentions/importAllMembers/TypeReference.kt");
}
@TestMetadata("TypeReference2.kt")
public void testTypeReference2() throws Exception {
runTest("idea/testData/intentions/importAllMembers/TypeReference2.kt");
}
@TestMetadata("UnresolvedMember.kt")
public void testUnresolvedMember() throws Exception {
runTest("idea/testData/intentions/importAllMembers/UnresolvedMember.kt");
}
}
}
@@ -0,0 +1,7 @@
import javax.swing.SwingUtilities
<spot>import javax.swing.SwingUtilities.*</spot>
fun foo() {
SwingUtilities.invokeLater { }
SwingUtilities.invokeAndWait()
}
@@ -0,0 +1,6 @@
import javax.swing.SwingUtilities
fun foo() {
<spot>SwingUtilities.</spot>invokeLater { }
<spot>SwingUtilities.</spot>invokeAndWait()
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts the selected qualified reference into a simple reference and adds an import for the corresponding class.
</body>
</html>
@@ -19,5 +19,10 @@
<className>org.jetbrains.kotlin.idea.fir.intentions.HLAddSetterIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.fir.intentions.HLImportAllMembersIntention</className>
<category>Kotlin</category>
</intentionAction>
</extensions>
</idea-plugin>
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.fir.intentions.HLImportAllMembersIntention