diff --git a/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt
index 140856a968c..cc88f6266ab 100644
--- a/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt
+++ b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt
@@ -28,10 +28,14 @@ import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.checker.KotlinTypeCheckerImpl
import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
-fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean {
+fun descriptorsEqualWithSubstitution(
+ descriptor1: DeclarationDescriptor?,
+ descriptor2: DeclarationDescriptor?,
+ checkOriginals: Boolean = true
+): Boolean {
if (descriptor1 == descriptor2) return true
if (descriptor1 == null || descriptor2 == null) return false
- if (descriptor1.original != descriptor2.original) return false
+ if (checkOriginals && descriptor1.original != descriptor2.original) return false
if (descriptor1 !is CallableDescriptor) return true
descriptor2 as CallableDescriptor
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index eef2b37024f..7b17ccbaf3f 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1645,7 +1645,12 @@
Kotlin
-
+ org.jetbrains.kotlin.idea.intentions.MoveMemberToTopLevelIntention
+ Kotlin
+
+
+ (KtNamedDeclaration::class.java, text) {
override fun startInWriteAction() = false
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveMemberToTopLevelIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveMemberToTopLevelIntention.kt
new file mode 100644
index 00000000000..1b340a54803
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveMemberToTopLevelIntention.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2017 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.util.TextRange
+import com.intellij.psi.PsiElement
+import com.intellij.refactoring.util.RefactoringUIUtil
+import com.intellij.util.containers.MultiMap
+import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
+import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
+import org.jetbrains.kotlin.util.descriptorsEqualWithSubstitution
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
+
+class MoveMemberToTopLevelIntention : MoveMemberOutOfObjectIntention("Move to top level") {
+ override fun addConflicts(element: KtNamedDeclaration, conflicts: MultiMap) {
+ val packageViewDescriptor = element.findModuleDescriptor().getPackage(element.containingKtFile.packageFqName)
+ val packageDescriptor = packageViewDescriptor.fragments.firstIsInstance()
+ val memberScope = packageDescriptor.getMemberScope()
+ val packageName = packageViewDescriptor.fqName.asString().takeIf { it.isNotBlank() } ?: "default"
+
+ val name = element.name ?: return
+
+ val isRedeclaration = when (element) {
+ is KtProperty -> memberScope.getVariableNames().any { name == it.identifier }
+
+ is KtFunction -> {
+ memberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_IDE).filter {
+ descriptorsEqualWithSubstitution(element.descriptor, it, false)
+ }.isNotEmpty()
+ }
+
+ else -> false
+ }
+
+ if (isRedeclaration) {
+ conflicts.putValue(element, "Package '$packageName' already contains ${RefactoringUIUtil.getDescription(element, false)}")
+ }
+ }
+
+ override fun getDestination(element: KtNamedDeclaration) = element.containingKtFile
+
+ override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
+ if (element !is KtNamedFunction && element !is KtProperty) return null
+ element.containingClassOrObject as? KtObjectDeclaration ?: return null
+ return element.nameIdentifier?.textRange
+ }
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/.intention b/idea/testData/intentions/moveMemberToTopLevel/.intention
new file mode 100644
index 00000000000..d17ee135a90
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.MoveMemberToTopLevelIntention
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/abstractFunction.kt b/idea/testData/intentions/moveMemberToTopLevel/abstractFunction.kt
new file mode 100644
index 00000000000..5f0b37191ed
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/abstractFunction.kt
@@ -0,0 +1,5 @@
+// IS_APPLICABLE: false
+
+abstract class A {
+ abstract fun foo()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/abstractProperty.kt b/idea/testData/intentions/moveMemberToTopLevel/abstractProperty.kt
new file mode 100644
index 00000000000..2562dc13eef
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/abstractProperty.kt
@@ -0,0 +1,5 @@
+// IS_APPLICABLE: false
+
+abstract class A {
+ abstract val foo: Int
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/function.kt b/idea/testData/intentions/moveMemberToTopLevel/function.kt
new file mode 100644
index 00000000000..0a2e6c62271
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/function.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+object A {
+ private fun foo() = 1
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/function.kt.after b/idea/testData/intentions/moveMemberToTopLevel/function.kt.after
new file mode 100644
index 00000000000..d7707d28b1f
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/function.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+private fun foo() = 1
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt b/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt
new file mode 100644
index 00000000000..41c2b06c4c4
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+
+class A {
+ companion object {
+ private fun foo() = 1
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt.after b/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt.after
new file mode 100644
index 00000000000..e9b5998eced
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+class A {
+}
+
+private fun foo() = 1
diff --git a/idea/testData/intentions/moveMemberToTopLevel/property.kt b/idea/testData/intentions/moveMemberToTopLevel/property.kt
new file mode 100644
index 00000000000..f04472954b8
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/property.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+object A {
+ val foo: Int = 1
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/property.kt.after b/idea/testData/intentions/moveMemberToTopLevel/property.kt.after
new file mode 100644
index 00000000000..9cb9f19b3a1
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/property.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val foo: Int = 1
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt b/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt
new file mode 100644
index 00000000000..d45e603d9d9
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+
+class A {
+ companion object {
+ val foo: Int = 1
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt.after b/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt.after
new file mode 100644
index 00000000000..bd1139da7e0
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+class A {
+}
+
+val foo: Int = 1
diff --git a/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt b/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt
new file mode 100644
index 00000000000..432df631c19
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt
@@ -0,0 +1,6 @@
+// SHOULD_FAIL_WITH: Package 'default' already contains function f1(Int)
+object Test {
+ fun f1(n: Int) {}
+}
+
+fun f1(n: Int) {}
diff --git a/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt b/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt
new file mode 100644
index 00000000000..8d1b1e5b518
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt
@@ -0,0 +1,8 @@
+// SHOULD_FAIL_WITH: Package 'foo.bar' already contains function f1(Int)
+package foo.bar
+
+object Test {
+ fun f1(n: Int) {}
+}
+
+fun f1(n: Int) {}
diff --git a/idea/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt b/idea/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt
new file mode 100644
index 00000000000..d081c814598
--- /dev/null
+++ b/idea/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt
@@ -0,0 +1,6 @@
+// SHOULD_FAIL_WITH: Package 'default' already contains property f1
+object Test {
+ val f1 = 1
+}
+
+val f1 = 1
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 4b4621a6a37..4334978b8d9 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -11706,6 +11706,72 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/moveMemberToTopLevel")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class MoveMemberToTopLevel extends AbstractIntentionTest {
+ @TestMetadata("abstractFunction.kt")
+ public void testAbstractFunction() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/abstractFunction.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("abstractProperty.kt")
+ public void testAbstractProperty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/abstractProperty.kt");
+ doTest(fileName);
+ }
+
+ public void testAllFilesPresentInMoveMemberToTopLevel() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/moveMemberToTopLevel"),
+ Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("function.kt")
+ public void testFunction() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/function.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("functionInCompanion.kt")
+ public void testFunctionInCompanion() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("property.kt")
+ public void testProperty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/property.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("propertyInCompanion.kt")
+ public void testPropertyInCompanion() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("redeclarationConflict.kt")
+ public void testRedeclarationConflict() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("redeclarationConflictWithPackage.kt")
+ public void testRedeclarationConflictWithPackage() throws Exception {
+ String fileName =
+ KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("redeclarationPropertyConflict.kt")
+ public void testRedeclarationPropertyConflict() throws Exception {
+ String fileName =
+ KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/moveOutOfCompanion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)