KTIJ-650 [Code completion]: support for "sealed interface"

^KTIJ-650 fixed
This commit is contained in:
Andrei Klunnyi
2020-12-03 19:05:54 +01:00
parent 602ed42b99
commit fe64b13140
25 changed files with 124 additions and 43 deletions
@@ -66,15 +66,15 @@ object KeywordCompletion {
private val KEYWORDS_TO_IGNORE_PREFIX = private val KEYWORDS_TO_IGNORE_PREFIX =
TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */) TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */)
private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, KtKeywordToken>( private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
COMPANION_KEYWORD to OBJECT_KEYWORD, COMPANION_KEYWORD to setOf(OBJECT_KEYWORD),
DATA_KEYWORD to CLASS_KEYWORD, DATA_KEYWORD to setOf(CLASS_KEYWORD),
ENUM_KEYWORD to CLASS_KEYWORD, ENUM_KEYWORD to setOf(CLASS_KEYWORD),
ANNOTATION_KEYWORD to CLASS_KEYWORD, ANNOTATION_KEYWORD to setOf(CLASS_KEYWORD),
SEALED_KEYWORD to CLASS_KEYWORD, SEALED_KEYWORD to setOf(CLASS_KEYWORD, INTERFACE_KEYWORD),
LATEINIT_KEYWORD to VAR_KEYWORD, LATEINIT_KEYWORD to setOf(VAR_KEYWORD),
CONST_KEYWORD to VAL_KEYWORD, CONST_KEYWORD to setOf(VAL_KEYWORD),
SUSPEND_KEYWORD to FUN_KEYWORD SUSPEND_KEYWORD to setOf(FUN_KEYWORD)
) )
private val KEYWORD_CONSTRUCTS = mapOf<KtKeywordToken, String>( private val KEYWORD_CONSTRUCTS = mapOf<KtKeywordToken, String>(
@@ -105,36 +105,56 @@ object KeywordCompletion {
SET_KEYWORD SET_KEYWORD
).map { it.value } + "companion object" ).map { it.value } + "companion object"
fun complete(position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean, consumer: (LookupElement) -> Unit) { fun complete(position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean, consumer: (LookupElement) -> Unit) {
if (!GENERAL_FILTER.isAcceptable(position, position)) return if (!GENERAL_FILTER.isAcceptable(position, position)) return
val parserFilter = buildFilter(position) val parserFilter = buildFilter(position)
for (keywordToken in ALL_KEYWORDS) { for (keywordToken in ALL_KEYWORDS) {
val nextKeywords = keywordToken.getNextPossibleKeywords(position) ?: setOf(null)
nextKeywords.forEach {
handleCompoundKeyword(position, keywordToken, it, isJvmModule, prefixMatcher, parserFilter, consumer)
}
}
}
private fun KtKeywordToken.getNextPossibleKeywords(position: PsiElement): Set<KtKeywordToken>? {
return when {
this == SUSPEND_KEYWORD && position.getStrictParentOfType<KtTypeReference>() != null -> null
else -> COMPOUND_KEYWORDS[this]
}
}
private fun handleCompoundKeyword(
position: PsiElement,
keywordToken: KtKeywordToken,
nextKeyword: KtKeywordToken?,
isJvmModule: Boolean,
prefixMatcher: PrefixMatcher,
parserFilter: (KtKeywordToken) -> Boolean,
consumer: (LookupElement) -> Unit
) {
var keyword = keywordToken.value var keyword = keywordToken.value
val nextKeyword = when {
keywordToken == SUSPEND_KEYWORD && position.getStrictParentOfType<KtTypeReference>() != null -> null
else -> COMPOUND_KEYWORDS[keywordToken]
}
var applicableAsCompound = false var applicableAsCompound = false
if (nextKeyword != null) { if (nextKeyword != null) {
fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText() fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText()
var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text
if (next != null && next.startsWith("$")) { next = next?.removePrefix("$")
next = next.substring(1)
} val nextIsNotYetPresent = keywordToken.getNextPossibleKeywords(position)?.none { it.value == next } == true
if (next != nextKeyword.value) if (nextIsNotYetPresent)
keyword += " " + nextKeyword.value keyword += " " + nextKeyword.value
else else
applicableAsCompound = true applicableAsCompound = true
} }
if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) continue // not supported for JVM if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) return // not supported for JVM
if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) continue if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) return
if (!parserFilter(keywordToken)) continue if (!parserFilter(keywordToken)) return
val constructText = KEYWORD_CONSTRUCTS[keywordToken] val constructText = KEYWORD_CONSTRUCTS[keywordToken]
if (constructText != null && !applicableAsCompound) { if (constructText != null && !applicableAsCompound) {
@@ -154,7 +174,6 @@ object KeywordCompletion {
consumer(createLookupElementBuilder(keyword, position)) consumer(createLookupElementBuilder(keyword, position))
} }
} }
}
private fun createLookupElementBuilder( private fun createLookupElementBuilder(
keyword: String, keyword: String,
@@ -33,6 +33,7 @@ class MouseMovedEventArgs
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -31,6 +31,7 @@ class AfterClasses {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
// EXIST: value // EXIST: value
@@ -33,6 +33,8 @@ class B {
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed class AfterClasses_LangLevel10 // EXIST: sealed class AfterClasses_LangLevel10
// EXIST: sealed interface AfterClasses_LangLevel10
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel10(...)", "attributes":"bold" } // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel10(...)", "attributes":"bold" }
// EXIST: inline // EXIST: inline
@@ -33,6 +33,8 @@ class B {
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed class AfterClasses_LangLevel11 // EXIST: sealed class AfterClasses_LangLevel11
// EXIST: sealed interface AfterClasses_LangLevel11
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel11(...)", "attributes":"bold" } // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel11(...)", "attributes":"bold" }
// EXIST: inline // EXIST: inline
+1
View File
@@ -32,6 +32,7 @@ class A {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -37,6 +37,8 @@ var a : Int
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed class GlobalPropertyAccessors // EXIST: sealed class GlobalPropertyAccessors
// EXIST: sealed interface GlobalPropertyAccessors
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" GlobalPropertyAccessors(...)", "attributes":"bold" } // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" GlobalPropertyAccessors(...)", "attributes":"bold" }
// EXIST: inline // EXIST: inline
@@ -18,6 +18,7 @@ annotation class Test {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -30,6 +30,7 @@ public class Test {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -24,6 +24,7 @@ class TestClass {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -18,6 +18,7 @@ enum class Test {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -20,6 +20,7 @@ interface Test {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -21,6 +21,7 @@ object Test {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -24,6 +24,8 @@ package Test
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed class InTopScopeAfterPackage // EXIST: sealed class InTopScopeAfterPackage
// EXIST: sealed interface InTopScopeAfterPackage
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" InTopScopeAfterPackage(...)", "attributes":"bold" } // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" InTopScopeAfterPackage(...)", "attributes":"bold" }
// EXIST: inline // EXIST: inline
@@ -32,6 +32,7 @@ class Some {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -32,6 +32,7 @@ class Some {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -30,6 +30,7 @@ class Some {
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: lateinit var // EXIST: lateinit var
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
@@ -0,0 +1,3 @@
seal<caret>class A
// EXIST: "sealed"
// NOTHING_ELSE
@@ -0,0 +1,3 @@
seal<caret>interface A
// EXIST: "sealed"
// NOTHING_ELSE
@@ -0,0 +1,6 @@
seal<caret>
// EXIST: "sealed class SealedWithName"
// EXIST: "sealed interface SealedWithName"
// EXIST: "sealed class"
// EXIST: "sealed interface"
// NOTHING_ELSE
@@ -0,0 +1,6 @@
class OuterClass {
seal<caret>
}
// EXIST: "sealed class"
// EXIST: "sealed interface"
// NOTHING_ELSE
+2
View File
@@ -23,6 +23,8 @@
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed class TopScope // EXIST: sealed class TopScope
// EXIST: sealed interface TopScope
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" TopScope(...)", "attributes":"bold" } // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" TopScope(...)", "attributes":"bold" }
// EXIST: inline // EXIST: inline
+1
View File
@@ -18,6 +18,7 @@
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
// EXIST: value // EXIST: value
+1
View File
@@ -18,6 +18,7 @@
// EXIST: operator // EXIST: operator
// EXIST: infix // EXIST: infix
// EXIST: sealed class // EXIST: sealed class
// EXIST: sealed interface
// EXIST: data class // EXIST: data class
// EXIST: inline // EXIST: inline
// EXIST: value // EXIST: value
@@ -503,6 +503,26 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes
runTest("idea/idea-completion/testData/keywords/ReturnSet.kt"); runTest("idea/idea-completion/testData/keywords/ReturnSet.kt");
} }
@TestMetadata("SealedForDeclaredClass.kt")
public void testSealedForDeclaredClass() throws Exception {
runTest("idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt");
}
@TestMetadata("SealedForDeclaredInterface.kt")
public void testSealedForDeclaredInterface() throws Exception {
runTest("idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt");
}
@TestMetadata("SealedWithName.kt")
public void testSealedWithName() throws Exception {
runTest("idea/idea-completion/testData/keywords/SealedWithName.kt");
}
@TestMetadata("SealedWithoutName.kt")
public void testSealedWithoutName() throws Exception {
runTest("idea/idea-completion/testData/keywords/SealedWithoutName.kt");
}
@TestMetadata("SuspendInParameterTypePosition.kt") @TestMetadata("SuspendInParameterTypePosition.kt")
public void testSuspendInParameterTypePosition() throws Exception { public void testSuspendInParameterTypePosition() throws Exception {
runTest("idea/idea-completion/testData/keywords/SuspendInParameterTypePosition.kt"); runTest("idea/idea-completion/testData/keywords/SuspendInParameterTypePosition.kt");