KT-4945 Intention "Introduce static import" (for single symbol)

#KT-4945 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-11-06 18:39:47 +03:00
parent 394b4f7cd4
commit 870fc9284b
16 changed files with 269 additions and 0 deletions
@@ -0,0 +1,7 @@
import javax.swing.SwingUtilities
<spot>import javax.swing.SwingUtilities.invokeLater</spot>
fun foo() {
<spot>invokeLater</spot> { }
<spot>invokeLater</spot>()
}
@@ -0,0 +1,6 @@
import javax.swing.SwingUtilities
fun foo() {
<spot>SwingUtilities.invokeLater</spot> { }
<spot>SwingUtilities.invokeLater</spot>()
}
@@ -0,0 +1,6 @@
<html>
<body>
This intention converts qualified references to a Java static member, object member or enum class member
into simple references and adds import directive for that symbol.
</body>
</html>
+5
View File
@@ -627,6 +627,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ImportMemberIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention</className>
<category>Kotlin</category>
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class ImportMemberIntention : SelfTargetingOffsetIndependentIntention<KtNameReferenceExpression>(
KtNameReferenceExpression::class.java,
"Add import for member"
){
override fun isApplicableTo(element: KtNameReferenceExpression): Boolean {
val qualifiedExpression = qualifiedExpression(element) ?: return false
val fqName = targetFqName(qualifiedExpression) ?: return false
text = "Add import for '${fqName.asString()}'"
return true
}
override fun applyTo(element: KtNameReferenceExpression, editor: Editor) {
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val targets = element.mainReference.resolveToDescriptors(bindingContext)
val fqName = targets.map { it.importableFqName!! }.single()
val file = element.getContainingKtFile()
val helper = ImportInsertHelper.getInstance(element.project)
if (helper.importDescriptor(file, targets.first()) == ImportInsertHelper.ImportDescriptorResult.FAIL) return
val qualifiedExpressions = file.collectDescendantsOfType<KtDotQualifiedExpression> { qualifiedExpression ->
val selector = qualifiedExpression.getQualifiedElementSelector() as? KtNameReferenceExpression
?: return@collectDescendantsOfType false
val qualifierName = qualifiedExpression.receiverExpression.getQualifiedElementSelector() as? KtNameReferenceExpression
?: return@collectDescendantsOfType false
selector.getReferencedNameAsName() == fqName.shortName()
&& qualifierName.getReferencedNameAsName() == fqName.parent().shortName()
&& targetFqName(qualifiedExpression) == fqName
}
//TODO: not deep
ShortenReferences.DEFAULT.process(qualifiedExpressions)
}
private fun qualifiedExpression(element: KtNameReferenceExpression): KtDotQualifiedExpression? {
val parent = element.parent
return (if (parent is KtCallExpression) parent.parent else parent) as? KtDotQualifiedExpression
}
private fun targetFqName(qualifiedExpression: KtDotQualifiedExpression): FqName? {
val nameExpression = qualifiedExpression.getQualifiedElementSelector() as? KtNameReferenceExpression ?: return null
val bindingContext = qualifiedExpression.analyze(BodyResolveMode.PARTIAL)
if (bindingContext[BindingContext.QUALIFIER, qualifiedExpression.receiverExpression] == null) return null
val targets = nameExpression.mainReference.resolveToDescriptors(bindingContext)
if (targets.isEmpty()) return null
if (!targets.all { it.canBeReferencedViaImport() }) return null
return targets.map { it.importableFqName }.singleOrNull()
}
}
+1
View File
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ImportMemberIntention
+7
View File
@@ -0,0 +1,7 @@
// INTENTION_TEXT: "Add import for 'kotlin.LazyThreadSafetyMode.NONE'"
// WITH_RUNTIME
class A {
val v1: Int by lazy(LazyThreadSafetyMode.NONE<caret>) { 1 }
val v2: Int by lazy(LazyThreadSafetyMode.NONE) { 1 }
}
@@ -0,0 +1,9 @@
import kotlin.LazyThreadSafetyMode.NONE
// INTENTION_TEXT: "Add import for 'kotlin.LazyThreadSafetyMode.NONE'"
// WITH_RUNTIME
class A {
val v1: Int by lazy(NONE<caret>) { 1 }
val v2: Int by lazy(NONE) { 1 }
}
+10
View File
@@ -0,0 +1,10 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
// ERROR: Unresolved reference: xxx
import javax.swing.SwingUtilities
fun foo() {
SwingUtilities.<caret>xxx {
}
}
+9
View File
@@ -0,0 +1,9 @@
// INTENTION_TEXT: "Add import for 'kotlin.properties.Delegates.notNull'"
// WITH_RUNTIME
import kotlin.properties.Delegates
class A {
val v1: Int by Delegates.notNull()
val v2: Char by Delegates.notNull<caret>()
}
@@ -0,0 +1,10 @@
// INTENTION_TEXT: "Add import for 'kotlin.properties.Delegates.notNull'"
// WITH_RUNTIME
import kotlin.properties.Delegates
import kotlin.properties.Delegates.notNull
class A {
val v1: Int by notNull()
val v2: Char by notNull<caret>()
}
@@ -0,0 +1,10 @@
// INTENTION_TEXT: "Add import for 'javax.swing.SwingConstants.CENTER'"
// WITH_RUNTIME
import javax.swing.SwingConstants
fun foo() {
val v = SwingConstants.CENTER
SwingConstants.<caret>CENTER
}
@@ -0,0 +1,11 @@
// INTENTION_TEXT: "Add import for 'javax.swing.SwingConstants.CENTER'"
// WITH_RUNTIME
import javax.swing.SwingConstants
import javax.swing.SwingConstants.CENTER
fun foo() {
val v = CENTER
<caret>CENTER
}
@@ -0,0 +1,26 @@
// INTENTION_TEXT: "Add import for 'javax.swing.SwingUtilities.invokeLater'"
// WITH_RUNTIME
// ERROR: No value passed for parameter p0
// ERROR: Unresolved reference: SomethingElse
// ERROR: Unresolved reference: somethingElse
import javax.swing.SwingUtilities
fun foo() {
SwingUtilities.<caret>invokeLater()
}
fun bar() {
javax.swing.SwingUtilities.invokeLater {
}
javax.swing.SwingUtilities.invokeLater(Runnable {
SwingUtilities.invokeLater { }
})
SwingUtilities.invokeAndWait { }
SomethingElse.invokeLater()
somethingElse.SwingUtilities.invokeLater()
}
@@ -0,0 +1,27 @@
// INTENTION_TEXT: "Add import for 'javax.swing.SwingUtilities.invokeLater'"
// WITH_RUNTIME
// ERROR: No value passed for parameter p0
// ERROR: Unresolved reference: SomethingElse
// ERROR: Unresolved reference: somethingElse
import javax.swing.SwingUtilities
import javax.swing.SwingUtilities.invokeLater
fun foo() {
<caret>invokeLater()
}
fun bar() {
invokeLater {
}
invokeLater(Runnable {
invokeLater { }
})
SwingUtilities.invokeAndWait { }
SomethingElse.invokeLater()
somethingElse.SwingUtilities.invokeLater()
}
@@ -5098,6 +5098,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/importMember")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ImportMember extends AbstractIntentionTest {
public void testAllFilesPresentInImportMember() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/importMember"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("EnumMember.kt")
public void testEnumMember() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/importMember/EnumMember.kt");
doTest(fileName);
}
@TestMetadata("NoTarget.kt")
public void testNoTarget() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/importMember/NoTarget.kt");
doTest(fileName);
}
@TestMetadata("ObjectMethod.kt")
public void testObjectMethod() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/importMember/ObjectMethod.kt");
doTest(fileName);
}
@TestMetadata("StaticJavaField.kt")
public void testStaticJavaField() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/importMember/StaticJavaField.kt");
doTest(fileName);
}
@TestMetadata("StaticJavaMethod.kt")
public void testStaticJavaMethod() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/importMember/StaticJavaMethod.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/infixCallToOrdinary")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)