Improve expect / actual markers on one line

Before this commit, only one-line enums and annotation classes were
considered. Now we can have enum header on one line and
some entries on another lines, and it still works.
Same with primary constructor parameters.

#KT-22637 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-12-26 16:43:40 +03:00
parent cdef811c55
commit 8de2ff2163
8 changed files with 121 additions and 13 deletions
@@ -63,7 +63,7 @@ fun getPlatformActualTooltip(declaration: KtDeclaration): String? {
}
fun KtDeclaration.allNavigatableActualDeclarations(): Set<KtDeclaration> =
actualsForExpected() + findMarkerBoundDeclarations().flatMap { it.actualsForExpected() }
actualsForExpected() + findMarkerBoundDeclarations().flatMap { it.actualsForExpected().asSequence() }
class ActualExpectedPsiElementCellRenderer : DefaultPsiElementCellRenderer() {
override fun getContainerText(element: PsiElement?, name: String?) = ""
@@ -385,22 +385,59 @@ private fun KtNamedDeclaration.requiresNoMarkers(
return true
}
is KtParameter,
is KtEnumEntry -> if (document?.areAnchorsOnOneLine(this, containingClassOrObject) == true) {
return true
is KtEnumEntry -> {
if (document?.areAnchorsOnOneLine(this, containingClassOrObject) == true) {
return true
}
if (this is KtEnumEntry) {
val enumEntries = containingClassOrObject?.body?.enumEntries.orEmpty()
val previousEnumEntry = enumEntries.getOrNull(enumEntries.indexOf(this) - 1)
if (document?.areAnchorsOnOneLine(this, previousEnumEntry) == true) {
return true
}
}
if (this is KtParameter && hasValOrVar()) {
val parameters = containingClassOrObject?.primaryConstructorParameters.orEmpty()
val previousParameter = parameters.getOrNull(parameters.indexOf(this) - 1)
if (document?.areAnchorsOnOneLine(this, previousParameter) == true) {
return true
}
}
}
}
return false
}
internal fun KtDeclaration.findMarkerBoundDeclarations(): List<KtNamedDeclaration> {
if (this !is KtClass) return emptyList()
val result = mutableListOf<KtNamedDeclaration>()
internal fun KtDeclaration.findMarkerBoundDeclarations(): Sequence<KtNamedDeclaration> {
if (this !is KtClass && this !is KtParameter) return emptySequence()
val document = PsiDocumentManager.getInstance(project).getDocument(containingFile)
result += primaryConstructor?.valueParameters?.filter { it.hasValOrVar() && it.requiresNoMarkers(document) }.orEmpty()
if (this.isEnum()) {
result += this.body?.enumEntries?.filter { it.requiresNoMarkers(document) }.orEmpty()
fun <T : KtNamedDeclaration> Sequence<T>.takeBound(bound: KtNamedDeclaration) = takeWhile {
document?.areAnchorsOnOneLine(bound, it) == true
}
return when (this) {
is KtParameter -> {
val propertyParameters = takeIf { hasValOrVar() }?.containingClassOrObject?.primaryConstructorParameters
?: return emptySequence()
propertyParameters.asSequence().dropWhile {
it !== this
}.drop(1).takeBound(this).filter { it.hasValOrVar() }
}
is KtEnumEntry -> {
val enumEntries = containingClassOrObject?.body?.enumEntries ?: return emptySequence()
enumEntries.asSequence().dropWhile {
it !== this
}.drop(1).takeBound(this)
}
is KtClass -> {
val boundParameters =
primaryConstructor?.valueParameters?.asSequence()?.takeBound(this)?.filter { it.hasValOrVar() }.orEmpty()
val boundEnumEntries = this.takeIf { isEnum() }?.body?.enumEntries?.asSequence()?.takeBound(this).orEmpty()
boundParameters + boundEnumEntries
}
else -> emptySequence()
}
return result
}
private fun collectActualMarkers(
@@ -0,0 +1,17 @@
package test
expect enum class <lineMarker descr="Has actuals in JVM">Enum</lineMarker> {
<lineMarker descr="Has actuals in JVM">A</lineMarker>, B, C, D
}
/*
LINEMARKER: Has actuals in JVM
TARGETS:
jvm.kt
actual enum class <1>Enum {
*//*
LINEMARKER: Has actuals in JVM
TARGETS:
jvm.kt
<1>A, <2>B, <3>C, <4>D
*/
@@ -0,0 +1,6 @@
// !CHECK_HIGHLIGHTING
package test
actual enum class Enum {
A, B, C, D
}
@@ -0,0 +1,6 @@
// !CHECK_HIGHLIGHTING
expect annotation class Ann(
val x: Int, val y: String,
val z: Double, val b: Boolean
)
@@ -0,0 +1,31 @@
actual annotation class <lineMarker descr="Has declaration in common module">Ann</lineMarker>(
actual val <lineMarker descr="Has declaration in common module">x</lineMarker>: Int, actual val y: String,
actual val <lineMarker descr="Has declaration in common module">z</lineMarker>: Double, actual val b: Boolean
)
// TODO: first marker on 'Ann' is generated twice here, see collectSlowLineMarkers main loop.
// Since it's fragile place, I don't want to fix it right now
/*
LINEMARKER: Has declaration in common module
TARGETS:
common.kt
expect annotation class <1>Ann(
*//*
LINEMARKER: Has declaration in common module
TARGETS:
common.kt
expect annotation class <1>Ann(
*//*
LINEMARKER: Has declaration in common module
TARGETS:
common.kt
val <1>x: Int, val <2>y: String,
*//*
LINEMARKER: Has declaration in common module
TARGETS:
common.kt
val <2>z: Double, val <1>b: Boolean
*/
@@ -69,6 +69,11 @@ public class MultiModuleLineMarkerTestGenerated extends AbstractMultiModuleLineM
runTest("idea/testData/multiModuleLineMarker/expectEnumEntriesInOneLine/");
}
@TestMetadata("expectEnumWithEnumEntriesInOneLine")
public void testExpectEnumWithEnumEntriesInOneLine() throws Exception {
runTest("idea/testData/multiModuleLineMarker/expectEnumWithEnumEntriesInOneLine/");
}
@TestMetadata("expectWithOverload")
public void testExpectWithOverload() throws Exception {
runTest("idea/testData/multiModuleLineMarker/expectWithOverload/");
@@ -79,6 +84,11 @@ public class MultiModuleLineMarkerTestGenerated extends AbstractMultiModuleLineM
runTest("idea/testData/multiModuleLineMarker/fromActualAnnotation/");
}
@TestMetadata("fromActualAnnotationWithParametersInOneLine")
public void testFromActualAnnotationWithParametersInOneLine() throws Exception {
runTest("idea/testData/multiModuleLineMarker/fromActualAnnotationWithParametersInOneLine/");
}
@TestMetadata("fromActualCompanion")
public void testFromActualCompanion() throws Exception {
runTest("idea/testData/multiModuleLineMarker/fromActualCompanion/");
@@ -117,16 +117,17 @@ abstract class AbstractLineMarkersTest : KotlinLightCodeInsightFixtureTestCase()
)
if (navigationDataComments.isEmpty()) return
for (navigationComment in navigationDataComments) {
for ((navigationCommentIndex, navigationComment) in navigationDataComments.reversed().withIndex()) {
val description = getLineMarkerDescription(navigationComment)
val navigateMarker = markers.find { it.lineMarkerTooltip?.startsWith(description) == true }!!
val navigateMarkers = markers.filter { it.lineMarkerTooltip?.startsWith(description) == true }
val navigateMarker = navigateMarkers.singleOrNull() ?: navigateMarkers.getOrNull(navigationCommentIndex)
TestCase.assertNotNull(
String.format("Can't find marker for navigation check with description \"%s\"", description),
navigateMarker
)
val handler = navigateMarker.navigationHandler
val handler = navigateMarker!!.navigationHandler
if (handler is TestableLineMarkerNavigator) {
val navigateElements = handler.getTargetsPopupDescriptor(navigateMarker.element)?.targets?.sortedBy {
it.renderAsGotoImplementation()