FIR IDE: Add ImportAllMembersIntention
This commit is contained in:
committed by
TeamCityServer
parent
8d69f32d04
commit
66d44f2471
+1
-1
@@ -1028,7 +1028,6 @@ fun main(args: Array<String>) {
|
|||||||
model("checker/diagnosticsMessage", excludedPattern = excludedFirTestdataPattern)
|
model("checker/diagnosticsMessage", excludedPattern = excludedFirTestdataPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
testClass<AbstractHighLevelQuickFixTest> {
|
testClass<AbstractHighLevelQuickFixTest> {
|
||||||
val pattern = "^([\\w\\-_]+)\\.kt$"
|
val pattern = "^([\\w\\-_]+)\\.kt$"
|
||||||
model("quickfix/abstract", pattern = pattern, filenameStartsLowerCase = true)
|
model("quickfix/abstract", pattern = pattern, filenameStartsLowerCase = true)
|
||||||
@@ -1067,6 +1066,7 @@ fun main(args: Array<String>) {
|
|||||||
val pattern = "^([\\w\\-_]+)\\.(kt|kts)$"
|
val pattern = "^([\\w\\-_]+)\\.(kt|kts)$"
|
||||||
model("intentions/addPropertyAccessors", pattern = pattern)
|
model("intentions/addPropertyAccessors", pattern = pattern)
|
||||||
model("intentions/specifyTypeExplicitly", pattern = pattern)
|
model("intentions/specifyTypeExplicitly", pattern = pattern)
|
||||||
|
model("intentions/importAllMembers", pattern = pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractFirShortenRefsTest> {
|
testClass<AbstractFirShortenRefsTest> {
|
||||||
|
|||||||
+116
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+83
@@ -473,4 +473,87 @@ public class HLIntentionTestGenerated extends AbstractHLIntentionTest {
|
|||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/unitType.kt");
|
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>
|
<className>org.jetbrains.kotlin.idea.fir.intentions.HLAddSetterIntention</className>
|
||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>org.jetbrains.kotlin.idea.fir.intentions.HLImportAllMembersIntention</className>
|
||||||
|
<category>Kotlin</category>
|
||||||
|
</intentionAction>
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.fir.intentions.HLImportAllMembersIntention
|
||||||
Reference in New Issue
Block a user