diff --git a/idea/resources/intentionDescriptions/ImportMemberIntention/after.kt.template b/idea/resources/intentionDescriptions/ImportMemberIntention/after.kt.template
new file mode 100644
index 00000000000..d5989c7e7e3
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ImportMemberIntention/after.kt.template
@@ -0,0 +1,7 @@
+import javax.swing.SwingUtilities
+import javax.swing.SwingUtilities.invokeLater
+
+fun foo() {
+ invokeLater { }
+ invokeLater()
+}
diff --git a/idea/resources/intentionDescriptions/ImportMemberIntention/before.kt.template b/idea/resources/intentionDescriptions/ImportMemberIntention/before.kt.template
new file mode 100644
index 00000000000..b08e56a217f
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ImportMemberIntention/before.kt.template
@@ -0,0 +1,6 @@
+import javax.swing.SwingUtilities
+
+fun foo() {
+ SwingUtilities.invokeLater { }
+ SwingUtilities.invokeLater()
+}
diff --git a/idea/resources/intentionDescriptions/ImportMemberIntention/description.html b/idea/resources/intentionDescriptions/ImportMemberIntention/description.html
new file mode 100644
index 00000000000..bad3b7d5601
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ImportMemberIntention/description.html
@@ -0,0 +1,6 @@
+
+
+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.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index eaa3985c911..826c7e74452 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -627,6 +627,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.ImportMemberIntention
+ Kotlin
+
+
org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
Kotlin
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ImportMemberIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ImportMemberIntention.kt
new file mode 100644
index 00000000000..465ea86d5ec
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ImportMemberIntention.kt
@@ -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::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 { 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()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/.intention b/idea/testData/intentions/importMember/.intention
new file mode 100644
index 00000000000..da84047aa11
--- /dev/null
+++ b/idea/testData/intentions/importMember/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.ImportMemberIntention
diff --git a/idea/testData/intentions/importMember/EnumMember.kt b/idea/testData/intentions/importMember/EnumMember.kt
new file mode 100644
index 00000000000..82a086dd37d
--- /dev/null
+++ b/idea/testData/intentions/importMember/EnumMember.kt
@@ -0,0 +1,7 @@
+// INTENTION_TEXT: "Add import for 'kotlin.LazyThreadSafetyMode.NONE'"
+// WITH_RUNTIME
+
+class A {
+ val v1: Int by lazy(LazyThreadSafetyMode.NONE) { 1 }
+ val v2: Int by lazy(LazyThreadSafetyMode.NONE) { 1 }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/EnumMember.kt.after b/idea/testData/intentions/importMember/EnumMember.kt.after
new file mode 100644
index 00000000000..ba89f2f360d
--- /dev/null
+++ b/idea/testData/intentions/importMember/EnumMember.kt.after
@@ -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) { 1 }
+ val v2: Int by lazy(NONE) { 1 }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/NoTarget.kt b/idea/testData/intentions/importMember/NoTarget.kt
new file mode 100644
index 00000000000..9b746166f17
--- /dev/null
+++ b/idea/testData/intentions/importMember/NoTarget.kt
@@ -0,0 +1,10 @@
+// IS_APPLICABLE: false
+// WITH_RUNTIME
+// ERROR: Unresolved reference: xxx
+
+import javax.swing.SwingUtilities
+
+fun foo() {
+ SwingUtilities.xxx {
+ }
+}
diff --git a/idea/testData/intentions/importMember/ObjectMethod.kt b/idea/testData/intentions/importMember/ObjectMethod.kt
new file mode 100644
index 00000000000..ebba24edeb0
--- /dev/null
+++ b/idea/testData/intentions/importMember/ObjectMethod.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/ObjectMethod.kt.after b/idea/testData/intentions/importMember/ObjectMethod.kt.after
new file mode 100644
index 00000000000..f99423d9879
--- /dev/null
+++ b/idea/testData/intentions/importMember/ObjectMethod.kt.after
@@ -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()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/StaticJavaField.kt b/idea/testData/intentions/importMember/StaticJavaField.kt
new file mode 100644
index 00000000000..685fb48f0d7
--- /dev/null
+++ b/idea/testData/intentions/importMember/StaticJavaField.kt
@@ -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.CENTER
+}
diff --git a/idea/testData/intentions/importMember/StaticJavaField.kt.after b/idea/testData/intentions/importMember/StaticJavaField.kt.after
new file mode 100644
index 00000000000..3dafbdc51b4
--- /dev/null
+++ b/idea/testData/intentions/importMember/StaticJavaField.kt.after
@@ -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
+
+ CENTER
+}
diff --git a/idea/testData/intentions/importMember/StaticJavaMethod.kt b/idea/testData/intentions/importMember/StaticJavaMethod.kt
new file mode 100644
index 00000000000..96e5332a568
--- /dev/null
+++ b/idea/testData/intentions/importMember/StaticJavaMethod.kt
@@ -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.invokeLater()
+}
+
+fun bar() {
+ javax.swing.SwingUtilities.invokeLater {
+ }
+
+ javax.swing.SwingUtilities.invokeLater(Runnable {
+ SwingUtilities.invokeLater { }
+ })
+
+ SwingUtilities.invokeAndWait { }
+
+ SomethingElse.invokeLater()
+
+ somethingElse.SwingUtilities.invokeLater()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/importMember/StaticJavaMethod.kt.after b/idea/testData/intentions/importMember/StaticJavaMethod.kt.after
new file mode 100644
index 00000000000..7a0320768d0
--- /dev/null
+++ b/idea/testData/intentions/importMember/StaticJavaMethod.kt.after
@@ -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() {
+ invokeLater()
+}
+
+fun bar() {
+ invokeLater {
+ }
+
+ invokeLater(Runnable {
+ invokeLater { }
+ })
+
+ SwingUtilities.invokeAndWait { }
+
+ SomethingElse.invokeLater()
+
+ somethingElse.SwingUtilities.invokeLater()
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 4acf911137f..e00f3d069e3 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -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)