diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtFile.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtFile.java index b9485f91726..105187585bb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtFile.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtFile.java @@ -20,10 +20,7 @@ import com.intellij.extapi.psi.PsiFileBase; import com.intellij.lang.ASTNode; import com.intellij.lang.FileASTNode; import com.intellij.openapi.fileTypes.FileType; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassOwner; -import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.*; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.PsiTreeUtil; import kotlin.ArraysKt; @@ -42,7 +39,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -public class KtFile extends PsiFileBase implements KtDeclarationContainer, KtAnnotated, KtElement, PsiClassOwner { +public class KtFile extends PsiFileBase implements KtDeclarationContainer, KtAnnotated, KtElement, PsiClassOwner, PsiNamedElement { private final boolean isCompiled; diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index d3c9bc46662..4bb3aef43cd 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.asJava import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -29,6 +30,12 @@ import java.util.* public fun KtClassOrObject.toLightClass(): KtLightClass? = LightClassUtil.getPsiClass(this) as KtLightClass? +public fun KtFile.findFacadeClass(): KtLightClass? { + return LightClassGenerationSupport.getInstance(project) + .getFacadeClassesInPackage(packageFqName, this.useScope as? GlobalSearchScope ?: GlobalSearchScope.projectScope(project)) + .firstOrNull { it is KtLightClassForFacade && this in it.files } as? KtLightClass +} + public fun KtDeclaration.toLightElements(): List = when (this) { is KtClassOrObject -> LightClassUtil.getPsiClass(this).singletonOrEmptyList() diff --git a/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinCreateTestIntention.kt b/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinCreateTestIntention.kt index 14f9830bba0..7777dc17b0d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinCreateTestIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinCreateTestIntention.kt @@ -33,6 +33,7 @@ import com.intellij.psi.search.GlobalSearchScopesCore import com.intellij.testIntegration.createTest.CreateTestAction import com.intellij.testIntegration.createTest.TestGenerators import org.jetbrains.kotlin.asJava.KtLightClass +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny @@ -44,30 +45,50 @@ import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.runWithAlternativeResolveEnabled import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.runWhenSmart -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtEnumEntry -import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.utils.addToStdlib.singletonList import java.util.* -class KotlinCreateTestIntention : SelfTargetingRangeIntention(KtClassOrObject::class.java, "Create test") { - override fun applicabilityRange(element: KtClassOrObject): TextRange? { - if (element.isLocal()) return null - if (element is KtEnumEntry) return null - if (element is KtClass && (element.isAnnotation() || element.isInterface())) return null +class KotlinCreateTestIntention : SelfTargetingRangeIntention(KtNamedDeclaration::class.java, "Create test") { + override fun applicabilityRange(element: KtNamedDeclaration): TextRange? { if (ModuleUtilCore.findModuleForPsiElement(element) == null) return null - if (element.resolveToDescriptorIfAny() == null) return null + if (element.nameIdentifier == null) return null - return TextRange( - element.startOffset, - element.getDelegationSpecifierList()?.startOffset ?: element.getBody()?.startOffset ?: element.endOffset - ) + if (element is KtClassOrObject) { + if (element.isLocal()) return null + if (element is KtEnumEntry) return null + if (element is KtClass && (element.isAnnotation() || element.isInterface())) return null + + if (element.resolveToDescriptorIfAny() == null) return null + + return TextRange( + element.startOffset, + element.getDelegationSpecifierList()?.startOffset ?: element.getBody()?.startOffset ?: element.endOffset + ) + } + + if (element.parent !is KtFile) return null + + if (element is KtNamedFunction) { + return TextRange((element.funKeyword ?: element.nameIdentifier!!).startOffset, element.nameIdentifier!!.endOffset) + } + + if (element is KtProperty) { + if (element.getter == null && element.delegate == null) return null + return TextRange(element.valOrVarKeyword.startOffset, element.nameIdentifier!!.endOffset) + } + + return null } - override fun applyTo(element: KtClassOrObject, editor: Editor) { + override fun applyTo(element: KtNamedDeclaration, editor: Editor) { + val lightClass = when (element) { + is KtClassOrObject -> element.toLightClass() + else -> element.getContainingKtFile().findFacadeClass() + } ?: return + object : CreateTestAction() { // Based on the com.intellij.testIntegration.createTest.JavaTestGenerator.createTestClass() private fun findTestClass(targetDirectory: PsiDirectory, className: String): PsiClass? { @@ -164,6 +185,6 @@ class KotlinCreateTestIntention : SelfTargetingRangeIntention(K } } } - }.invoke(element.project, editor, element.toLightClass()!!) + }.invoke(element.project, editor, lightClass) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinTestFinder.kt b/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinTestFinder.kt index 33cacbb61a0..fdde9dafb3e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinTestFinder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/testIntegration/KotlinTestFinder.kt @@ -27,10 +27,10 @@ import com.intellij.testIntegration.JavaTestFinder import com.intellij.testIntegration.TestFinderHelper import com.intellij.util.CommonProcessors import com.intellij.util.containers.HashSet -import org.jetbrains.kotlin.asJava.KtLightClassForExplicitDeclaration -import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.asJava.* import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import java.util.* import java.util.regex.Pattern @@ -41,9 +41,11 @@ class KotlinTestFinder : JavaTestFinder() { override fun findSourceElement(from: PsiElement): PsiClass? { super.findSourceElement(from)?.let { return it } - val classOrObject = from.parentsWithSelf.filterIsInstance().firstOrNull { !it.isLocal() } ?: return null - if (classOrObject.resolveToDescriptorIfAny() == null) return null - return classOrObject.toLightClass() + from.parentsWithSelf.filterIsInstance().firstOrNull { !it.isLocal() }?.let { + return if (it.resolveToDescriptorIfAny() == null) null else it.toLightClass() + } + + return (from.containingFile as? KtFile)?.findFacadeClass() } override fun isTest(element: PsiElement): Boolean { @@ -63,9 +65,13 @@ class KotlinTestFinder : JavaTestFinder() { for (candidateNameWithWeight in TestFinderHelper.collectPossibleClassNamesWithWeights(klass.name)) { for (eachClass in cache.getClassesByName(candidateNameWithWeight.first, scope)) { if (eachClass.isAnnotationType || frameworks.isTestClass(eachClass)) continue - if (!eachClass.isPhysical && eachClass !is KtLightClassForExplicitDeclaration) continue - classesWithWeights.add(Pair.create(eachClass, candidateNameWithWeight.second)) + if (eachClass is KtLightClassForFacade) { + eachClass.files.mapTo(classesWithWeights) { Pair.create(it, candidateNameWithWeight.second) } + } + else if (eachClass.isPhysical || eachClass is KtLightClassForExplicitDeclaration) { + classesWithWeights.add(Pair.create(eachClass, candidateNameWithWeight.second)) + } } } diff --git a/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.2.kt b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.2.kt new file mode 100644 index 00000000000..cd721488c35 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.2.kt @@ -0,0 +1,7 @@ +@file:JvmName("FooUtils") +@file:JvmMultifileClass +import junit.framework.TestCase + +fun bar() { + +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.kt b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.kt new file mode 100644 index 00000000000..1a51da681d9 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.kt @@ -0,0 +1,13 @@ +@file:JvmName("FooUtils") +@file:JvmMultifileClass +import junit.framework.TestCase + +class Foo + +fun foo() { + +} + +val x = 1 + +@JvmField val y = 2 \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.main.java b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.main.java new file mode 100644 index 00000000000..d4ed830f6e1 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.main.java @@ -0,0 +1,9 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// REF: ().Foo +// REF: /src.fromJavaTestToKotlinFile.kt +// REF: /src.fromJavaTestToKotlinFile.2.kt +import junit.framework.TestCase; + +public class FooUtilsTest extends TestCase { + +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.java b/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.java new file mode 100644 index 00000000000..7344ed7826e --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.java @@ -0,0 +1,5 @@ +import junit.framework.TestCase; + +public class FooUtilsTest2 extends TestCase { + +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.main.kt b/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.main.kt new file mode 100644 index 00000000000..08101009c30 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.main.kt @@ -0,0 +1,19 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// REF: ().FooUtilsTest +// REF: FooUtilsTest2 +@file:JvmName("FooUtils") +import junit.framework.TestCase + +class Foo + +fun foo() { + +} + +val x = 1 + +@JvmField val y = 2 + +class FooUtilsTest : TestCase() { + +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.2.kt b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.2.kt new file mode 100644 index 00000000000..cd721488c35 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.2.kt @@ -0,0 +1,7 @@ +@file:JvmName("FooUtils") +@file:JvmMultifileClass +import junit.framework.TestCase + +fun bar() { + +} \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.kt b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.kt new file mode 100644 index 00000000000..bb7fafb4fce --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.kt @@ -0,0 +1,11 @@ +@file:JvmName("FooUtils") +@file:JvmMultifileClass +import junit.framework.TestCase + +fun foo() { + +} + +val x = 1 + +@JvmField val y = 2 \ No newline at end of file diff --git a/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.main.kt b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.main.kt new file mode 100644 index 00000000000..3f432d17fd5 --- /dev/null +++ b/idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.main.kt @@ -0,0 +1,8 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// REF: /src.fromKotlinTestToKotlinFile.kt +// REF: /src.fromKotlinTestToKotlinFile.2.kt +import junit.framework.TestCase + +class FooUtilsTest : TestCase() + +class FooUtilsTest2 : TestCase() \ No newline at end of file diff --git a/idea/testData/quickfix/migration/conflictingExtension/varInsteadOfVal.kt b/idea/testData/quickfix/migration/conflictingExtension/varInsteadOfVal.kt index a8b1430e0d4..c559083cca8 100644 --- a/idea/testData/quickfix/migration/conflictingExtension/varInsteadOfVal.kt +++ b/idea/testData/quickfix/migration/conflictingExtension/varInsteadOfVal.kt @@ -1,4 +1,5 @@ // "Delete redundant extension property" "false" +// ACTION: Create test import java.io.File var File.name: String diff --git a/idea/testData/quickfix/migration/conflictingExtension/wrongGetter.kt b/idea/testData/quickfix/migration/conflictingExtension/wrongGetter.kt index 5afe2572e81..4fdb0e10b57 100644 --- a/idea/testData/quickfix/migration/conflictingExtension/wrongGetter.kt +++ b/idea/testData/quickfix/migration/conflictingExtension/wrongGetter.kt @@ -1,4 +1,5 @@ // "Delete redundant extension property" "false" +// ACTION: Create test var Thread.priority: Int get() = getPriority() + 1 diff --git a/idea/testData/quickfix/migration/conflictingExtension/wrongGetter2.kt b/idea/testData/quickfix/migration/conflictingExtension/wrongGetter2.kt index ee8bb758fa8..efb1405c2af 100644 --- a/idea/testData/quickfix/migration/conflictingExtension/wrongGetter2.kt +++ b/idea/testData/quickfix/migration/conflictingExtension/wrongGetter2.kt @@ -1,5 +1,6 @@ // "Delete redundant extension property" "false" // ACTION: Convert property to function +// ACTION: Create test import java.io.File public val File.parent: File? diff --git a/idea/testData/quickfix/migration/conflictingExtension/wrongSetter.kt b/idea/testData/quickfix/migration/conflictingExtension/wrongSetter.kt index 5ac5e1e55fc..da5fc66bc37 100644 --- a/idea/testData/quickfix/migration/conflictingExtension/wrongSetter.kt +++ b/idea/testData/quickfix/migration/conflictingExtension/wrongSetter.kt @@ -1,4 +1,5 @@ // "Delete redundant extension property" "false" +// ACTION: Create test var Thread.priority: Int get() = this.getPriority() diff --git a/idea/tests/org/jetbrains/kotlin/idea/actions/GotoTestOrCodeActionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/actions/GotoTestOrCodeActionTestGenerated.java index b98a1074d65..3818c6dc31d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/actions/GotoTestOrCodeActionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/actions/GotoTestOrCodeActionTestGenerated.java @@ -47,12 +47,24 @@ public class GotoTestOrCodeActionTestGenerated extends AbstractGotoTestOrCodeAct doTest(fileName); } + @TestMetadata("fromJavaTestToKotlinFile.main.java") + public void testFromJavaTestToKotlinFile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinFile.main.java"); + doTest(fileName); + } + @TestMetadata("fromKotlinClassToTest.main.kt") public void testFromKotlinClassToTest() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinClassToTest.main.kt"); doTest(fileName); } + @TestMetadata("fromKotlinFileToTest.main.kt") + public void testFromKotlinFileToTest() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinFileToTest.main.kt"); + doTest(fileName); + } + @TestMetadata("fromKotlinTestToJavaClass.main.kt") public void testFromKotlinTestToJavaClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToJavaClass.main.kt"); @@ -64,4 +76,10 @@ public class GotoTestOrCodeActionTestGenerated extends AbstractGotoTestOrCodeAct String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinClass.main.kt"); doTest(fileName); } + + @TestMetadata("fromKotlinTestToKotlinFile.main.kt") + public void testFromKotlinTestToKotlinFile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinFile.main.kt"); + doTest(fileName); + } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java b/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java index e58f1413e9b..d0fe9d98e24 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java +++ b/idea/tests/org/jetbrains/kotlin/idea/navigation/NavigationTestUtils.java @@ -30,6 +30,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.testFramework.UsefulTestCase; +import com.intellij.util.PathUtil; import com.intellij.util.containers.MultiMap; import junit.framework.TestCase; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,10 @@ public final class NavigationTestUtils { public static void assertGotoDataMatching(Editor editor, GotoTargetHandler.GotoData gotoData) { // Get expected references from the tested document List expectedReferences = InTextDirectivesUtils.findListWithPrefixes(editor.getDocument().getText(), "// REF:"); + for (int i = 0; i < expectedReferences.size(); i++) { + expectedReferences.set(i, PathUtil.toSystemDependentName(expectedReferences.get(i)).replace("//", "/")); + } + Collections.sort(expectedReferences); if (gotoData != null) {