Navigation: Support "Navigate/Related Symbol" for expects/actuals

#KT-20952 Fixed
This commit is contained in:
Alexey Sedunov
2017-11-01 18:17:17 +03:00
parent b2e5e9dab5
commit b20ffe76b7
42 changed files with 386 additions and 41 deletions
+2
View File
@@ -858,6 +858,8 @@
<library.type implementation="org.jetbrains.kotlin.idea.framework.JSLibraryType"/>
<library.type implementation="org.jetbrains.kotlin.idea.framework.CommonLibraryType"/>
<gotoRelatedProvider implementation="org.jetbrains.kotlin.idea.goto.KotlinExpectOrActualGotoRelatedProvider"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>
@@ -0,0 +1,47 @@
/*
* 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.goto
import com.intellij.navigation.GotoRelatedItem
import com.intellij.navigation.GotoRelatedProvider
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.highlighter.markers.actualsForExpected
import org.jetbrains.kotlin.idea.highlighter.markers.expectedDeclarationIfAny
import org.jetbrains.kotlin.idea.highlighter.markers.isActualDeclaration
import org.jetbrains.kotlin.idea.highlighter.markers.isExpectDeclaration
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
class KotlinExpectOrActualGotoRelatedProvider : GotoRelatedProvider() {
private class ActualOrExpectGotoRelatedItem(element: PsiElement): GotoRelatedItem(element) {
override fun getCustomContainerName(): String? {
val module = element?.module ?: return null
return "(in module ${module.name})"
}
}
override fun getItems(psiElement: PsiElement): List<GotoRelatedItem> {
val declaration = psiElement.getParentOfTypeAndBranch<KtNamedDeclaration> { nameIdentifier } ?: return emptyList()
val targets = when {
declaration.isExpectDeclaration() -> declaration.actualsForExpected()
declaration.isActualDeclaration() -> listOfNotNull(declaration.expectedDeclarationIfAny())
else -> emptyList()
}
return targets.map(::ActualOrExpectGotoRelatedItem)
}
}
@@ -65,12 +65,6 @@ class KotlinLineMarkerProvider : LineMarkerProvider {
return null
}
private fun KtNamedDeclaration.isExpectDeclaration(): Boolean =
(toDescriptor() as? MemberDescriptor)?.isExpect == true
private fun KtNamedDeclaration.isActualDeclaration(): Boolean =
(toDescriptor() as? MemberDescriptor)?.isActual == true
override fun collectSlowLineMarkers(elements: List<PsiElement>, result: MutableCollection<LineMarkerInfo<*>>) {
if (elements.isEmpty()) return
@@ -120,6 +114,12 @@ class KotlinLineMarkerProvider : LineMarkerProvider {
}
}
internal fun KtNamedDeclaration.isExpectDeclaration(): Boolean =
(toDescriptor() as? MemberDescriptor)?.isExpect == true
internal fun KtNamedDeclaration.isActualDeclaration(): Boolean =
(toDescriptor() as? MemberDescriptor)?.isActual == true
private val OVERRIDING_MARK: Icon = AllIcons.Gutter.OverridingMethod
private val IMPLEMENTING_MARK: Icon = AllIcons.Gutter.ImplementingMethod
private val OVERRIDDEN_MARK: Icon = AllIcons.Gutter.OverridenMethod
@@ -0,0 +1,5 @@
package test
expect class Foo {
fun bar()
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual fun bar() {}
}
@@ -0,0 +1,7 @@
package test
actual class Foo {
actual fun <caret>bar() {}
}
// REF: [common] (in test.Foo).bar()
@@ -0,0 +1,5 @@
package test
expect class Foo {
val bar: Int
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual val bar: Int get() = 1
}
@@ -0,0 +1,7 @@
package test
actual class Foo {
actual val <caret>bar: Int get() = 1
}
// REF: [common] (in test.Foo).bar
@@ -0,0 +1,8 @@
package test
expect class Foo {
fun <caret>bar()
}
// REF: [jvm] (in test.Foo).bar()
// REF: [js] (in test.Foo).bar()
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual fun bar() {}
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual fun bar() {}
}
@@ -0,0 +1,8 @@
package test
expect class Foo {
val <caret>bar: Int
}
// REF: [jvm] (in test.Foo).bar
// REF: [js] (in test.Foo).bar
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual val bar: Int get() = 1
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual val bar: Int get() = 1
}
@@ -0,0 +1,5 @@
package test
expect class Foo {
class Bar
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual class Bar
}
@@ -0,0 +1,7 @@
package test
actual class Foo {
actual class <caret>Bar
}
// REF: [common] (in test.Foo).Bar
@@ -0,0 +1,8 @@
package test
expect class Foo {
class <caret>Bar
}
// REF: [js] (in test.Foo).Bar
// REF: [jvm] (in test.Foo).Bar
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual class Bar
}
@@ -0,0 +1,5 @@
package test
actual class Foo {
actual class Bar
}
@@ -0,0 +1,3 @@
package test
expect class Foo
@@ -0,0 +1,3 @@
package test
actual class Foo
@@ -0,0 +1,5 @@
package test
actual class <caret>Foo
// REF: [common] (test).Foo
@@ -0,0 +1,3 @@
package test
expect fun foo()
@@ -0,0 +1,3 @@
package test
actual fun foo() {}
@@ -0,0 +1,5 @@
package test
actual fun <caret>foo() {}
// REF: [common] (test).foo()
@@ -0,0 +1,3 @@
package test
expect val foo: Int
@@ -0,0 +1,3 @@
package test
actual val foo: Int get() = 1
@@ -0,0 +1,5 @@
package test
actual val <caret>foo: Int get() = 1
// REF: [common] (test).foo
@@ -0,0 +1,6 @@
package test
expect class <caret>Foo
// REF: [jvm] (test).Foo
// REF: [js] (test).Foo
@@ -0,0 +1,3 @@
package test
actual class Foo
@@ -0,0 +1,3 @@
package test
actual class Foo
@@ -0,0 +1,6 @@
package test
expect fun <caret>foo()
// REF: [jvm] (test).foo()
// REF: [js] (test).foo()
@@ -0,0 +1,3 @@
package test
actual fun foo() {}
@@ -0,0 +1,3 @@
package test
actual fun foo() {}
@@ -0,0 +1,6 @@
package test
expect val <caret>foo: Int
// REF: [jvm] (test).foo
// REF: [js] (test).foo
@@ -0,0 +1,3 @@
package test
actual val foo: Int get() = 1
@@ -0,0 +1,3 @@
package test
actual val foo: Int get() = 1
@@ -0,0 +1,70 @@
/*
* 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.navigation
import com.intellij.codeInsight.navigation.GotoTargetHandler
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
import org.jetbrains.kotlin.idea.stubs.createFacet
import org.jetbrains.kotlin.idea.test.extractMarkerOffset
abstract class AbstractKotlinNavigationMultiModuleTest : AbstractMultiModuleTest() {
protected abstract fun doNavigate(editor: Editor, file: PsiFile): GotoTargetHandler.GotoData
protected fun doMultiPlatformTest(
testFileName: String,
commonModuleName: String = "common",
vararg actuals: Pair<String, TargetPlatformKind<*>> = arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6])
) {
val commonModule = module(commonModuleName)
commonModule.createFacet(TargetPlatformKind.Common, false)
actuals.forEach { (actualName, actualKind) ->
val implModule = module(actualName)
implModule.createFacet(actualKind, implementedModuleName = commonModuleName)
implModule.enableMultiPlatform()
implModule.addDependency(commonModule)
}
val file = PluginJetFilesProvider.allFilesInProject(myProject).single { it.name == testFileName }
val doc = PsiDocumentManager.getInstance(myProject).getDocument(file)!!
val offset = doc.extractMarkerOffset(project, "<caret>")
val editor = EditorFactory.getInstance().createEditor(doc, myProject)
editor.caretModel.moveToOffset(offset)
try {
val gotoData = doNavigate(editor, file)
NavigationTestUtils.assertGotoDataMatching(editor, gotoData, true)
}
finally {
EditorFactory.getInstance().releaseEditor(editor)
}
}
protected fun doMultiPlatformTestJvmJs(testFileName: String, commonModuleName: String = "common") {
doMultiPlatformTest(
testFileName,
commonModuleName,
*arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], "js" to TargetPlatformKind.JavaScript)
)
}
}
@@ -16,50 +16,19 @@
package org.jetbrains.kotlin.idea.navigation
import com.intellij.openapi.editor.EditorFactory
import com.intellij.psi.PsiDocumentManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
import org.jetbrains.kotlin.idea.stubs.createFacet
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.test.extractMarkerOffset
import java.io.File
class KotlinGotoImplementationMultiModuleTest : AbstractMultiModuleTest() {
class KotlinGotoImplementationMultiModuleTest : AbstractKotlinNavigationMultiModuleTest() {
override fun getTestDataPath(): String {
return File(PluginTestCaseBase.getTestDataPathBase(), "/navigation/implementations/multiModule").path + File.separator
}
private fun doMultiPlatformTest(
testFileName: String,
commonModuleName: String = "common",
vararg actuals: Pair<String, TargetPlatformKind<*>> = arrayOf("jvm" to TargetPlatformKind.Jvm[JvmTarget.JVM_1_6])
) {
val commonModule = module(commonModuleName)
commonModule.createFacet(TargetPlatformKind.Common, false)
actuals.forEach { (actualName, actualKind) ->
val implModule = module(actualName)
implModule.createFacet(actualKind, implementedModuleName = commonModuleName)
implModule.enableMultiPlatform()
implModule.addDependency(commonModule)
}
val file = PluginJetFilesProvider.allFilesInProject(myProject).single { it.name == testFileName }
val doc = PsiDocumentManager.getInstance(myProject).getDocument(file)!!
val offset = doc.extractMarkerOffset(project, "<caret>")
val editor = EditorFactory.getInstance().createEditor(doc, myProject)
editor.caretModel.moveToOffset(offset)
try {
val gotoData = NavigationTestUtils.invokeGotoImplementations(editor, file)
NavigationTestUtils.assertGotoDataMatching(editor, gotoData, true)
}
finally {
EditorFactory.getInstance().releaseEditor(editor)
}
}
override fun doNavigate(editor: Editor, file: PsiFile) = NavigationTestUtils.invokeGotoImplementations(editor, file)
fun testSuspendFunImpl() {
doMultiPlatformTest(
@@ -0,0 +1,83 @@
/*
* 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.navigation
import com.intellij.codeInsight.navigation.GotoTargetHandler
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import java.io.File
class KotlinGotoRelatedSymbolMultiModuleTest : AbstractKotlinNavigationMultiModuleTest() {
override fun getTestDataPath() =
File(PluginTestCaseBase.getTestDataPathBase(), "/navigation/relatedSymbols/multiModule").path + File.separator
override fun doNavigate(editor: Editor, file: PsiFile): GotoTargetHandler.GotoData {
val source = file.findElementAt(editor.caretModel.offset)!!
val relatedItems = NavigationUtil.collectRelatedItems(source, null)
return GotoTargetHandler.GotoData(source, relatedItems.map { it.element }.toTypedArray(), emptyList())
}
fun testFromTopLevelExpectClassToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromTopLevelActualClassToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
fun testFromTopLevelExpectFunToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromTopLevelActualFunToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
fun testFromTopLevelExpectValToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromTopLevelActualValToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
fun testFromNestedExpectClassToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromNestedActualClassToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
fun testFromExpectMemberFunToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromActualMemberFunToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
fun testFromExpectMemberValToActuals() {
doMultiPlatformTestJvmJs("common.kt")
}
fun testFromActualMemberValToExpect() {
doMultiPlatformTestJvmJs("jvm.kt")
}
}