KTIJ-650 [Code completion]: support for "sealed interface"
^KTIJ-650 fixed
This commit is contained in:
+62
-43
@@ -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,54 +105,73 @@ 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) {
|
||||||
var keyword = keywordToken.value
|
val nextKeywords = keywordToken.getNextPossibleKeywords(position) ?: setOf(null)
|
||||||
|
nextKeywords.forEach {
|
||||||
val nextKeyword = when {
|
handleCompoundKeyword(position, keywordToken, it, isJvmModule, prefixMatcher, parserFilter, consumer)
|
||||||
keywordToken == SUSPEND_KEYWORD && position.getStrictParentOfType<KtTypeReference>() != null -> null
|
|
||||||
else -> COMPOUND_KEYWORDS[keywordToken]
|
|
||||||
}
|
}
|
||||||
var applicableAsCompound = false
|
}
|
||||||
if (nextKeyword != null) {
|
}
|
||||||
fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText()
|
|
||||||
|
|
||||||
var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text
|
private fun KtKeywordToken.getNextPossibleKeywords(position: PsiElement): Set<KtKeywordToken>? {
|
||||||
if (next != null && next.startsWith("$")) {
|
return when {
|
||||||
next = next.substring(1)
|
this == SUSPEND_KEYWORD && position.getStrictParentOfType<KtTypeReference>() != null -> null
|
||||||
}
|
else -> COMPOUND_KEYWORDS[this]
|
||||||
if (next != nextKeyword.value)
|
}
|
||||||
keyword += " " + nextKeyword.value
|
}
|
||||||
else
|
|
||||||
applicableAsCompound = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) continue // not supported for JVM
|
private fun handleCompoundKeyword(
|
||||||
|
position: PsiElement,
|
||||||
|
keywordToken: KtKeywordToken,
|
||||||
|
nextKeyword: KtKeywordToken?,
|
||||||
|
isJvmModule: Boolean,
|
||||||
|
prefixMatcher: PrefixMatcher,
|
||||||
|
parserFilter: (KtKeywordToken) -> Boolean,
|
||||||
|
consumer: (LookupElement) -> Unit
|
||||||
|
) {
|
||||||
|
var keyword = keywordToken.value
|
||||||
|
|
||||||
if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) continue
|
var applicableAsCompound = false
|
||||||
|
if (nextKeyword != null) {
|
||||||
|
fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText()
|
||||||
|
|
||||||
if (!parserFilter(keywordToken)) continue
|
var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text
|
||||||
|
next = next?.removePrefix("$")
|
||||||
|
|
||||||
val constructText = KEYWORD_CONSTRUCTS[keywordToken]
|
val nextIsNotYetPresent = keywordToken.getNextPossibleKeywords(position)?.none { it.value == next } == true
|
||||||
if (constructText != null && !applicableAsCompound) {
|
if (nextIsNotYetPresent)
|
||||||
val element = createKeywordConstructLookupElement(position.project, keyword, constructText)
|
keyword += " " + nextKeyword.value
|
||||||
consumer(element)
|
else
|
||||||
} else {
|
applicableAsCompound = true
|
||||||
if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) {
|
}
|
||||||
val topLevelClassName = getTopLevelClassName(position)
|
|
||||||
if (topLevelClassName != null) {
|
if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) return // not supported for JVM
|
||||||
if (keyword.startsWith(DATA_KEYWORD.value)) {
|
|
||||||
consumer(createKeywordConstructLookupElement(position.project, keyword, "$keyword $topLevelClassName(caret)"))
|
if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) return
|
||||||
} else {
|
|
||||||
consumer(createLookupElementBuilder("$keyword $topLevelClassName", position))
|
if (!parserFilter(keywordToken)) return
|
||||||
}
|
|
||||||
|
val constructText = KEYWORD_CONSTRUCTS[keywordToken]
|
||||||
|
if (constructText != null && !applicableAsCompound) {
|
||||||
|
val element = createKeywordConstructLookupElement(position.project, keyword, constructText)
|
||||||
|
consumer(element)
|
||||||
|
} else {
|
||||||
|
if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) {
|
||||||
|
val topLevelClassName = getTopLevelClassName(position)
|
||||||
|
if (topLevelClassName != null) {
|
||||||
|
if (keyword.startsWith(DATA_KEYWORD.value)) {
|
||||||
|
consumer(createKeywordConstructLookupElement(position.project, keyword, "$keyword $topLevelClassName(caret)"))
|
||||||
|
} else {
|
||||||
|
consumer(createLookupElementBuilder("$keyword $topLevelClassName", position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumer(createLookupElementBuilder(keyword, position))
|
|
||||||
}
|
}
|
||||||
|
consumer(createLookupElementBuilder(keyword, position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+20
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user