Refine default postfix templates selector
There is new settings checkCanBeUsedAsValue Also expression filtering are split into to parts: - getNonFilteredExpressions is trivially goes until it reaches first block body of some declarations - all additional filtering is merged into Condition that is passed into PostfixTemplateExpressionSelectorBase super-call Notable changes for users: - do not suggest try for non-top-level expressions - do not suggest var/par/etc for loops
This commit is contained in:
+46
-29
@@ -103,23 +103,43 @@ internal object KtPostfixTemplatePsiInfo : PostfixTemplatePsiInfo() {
|
||||
}
|
||||
|
||||
internal fun createExpressionSelector(
|
||||
// Do not suggest expressions like 'val a = 1'/'for ...'
|
||||
checkCanBeUsedAsValue: Boolean = true,
|
||||
statementsOnly: Boolean = false,
|
||||
predicate: ((KotlinType) -> Boolean)? = null
|
||||
): PostfixTemplateExpressionSelectorBase =
|
||||
KtExpressionPostfixTemplateSelector {
|
||||
if (this !is KtExpression) return@KtExpressionPostfixTemplateSelector false
|
||||
if (statementsOnly && !isStatement()) return@KtExpressionPostfixTemplateSelector false
|
||||
|
||||
if (predicate == null) return@KtExpressionPostfixTemplateSelector true
|
||||
|
||||
getType(analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION))?.let { predicate(it) } ?: false
|
||||
}
|
||||
|
||||
private fun PsiElement.isStatement() = parent is KtBlockExpression
|
||||
typePredicate: ((KotlinType) -> Boolean)? = null
|
||||
): PostfixTemplateExpressionSelectorBase = KtExpressionPostfixTemplateSelector(statementsOnly, checkCanBeUsedAsValue, typePredicate)
|
||||
|
||||
private class KtExpressionPostfixTemplateSelector(
|
||||
filter: PsiElement.() -> Boolean
|
||||
) : PostfixTemplateExpressionSelectorBase(Condition(filter)) {
|
||||
statementsOnly: Boolean,
|
||||
checkCanBeUsedAsValue: Boolean,
|
||||
typePredicate: ((KotlinType) -> Boolean)?
|
||||
) : PostfixTemplateExpressionSelectorBase(createFilter(statementsOnly, checkCanBeUsedAsValue, typePredicate)) {
|
||||
companion object {
|
||||
private fun createFilter(
|
||||
statementsOnly: Boolean,
|
||||
checkCanBeUsedAsValue: Boolean,
|
||||
typePredicate: ((KotlinType) -> Boolean)?
|
||||
): Condition<PsiElement> = Condition {
|
||||
if (it !is KtExpression) return@Condition false
|
||||
|
||||
// Can't be independent expressions
|
||||
if (it.isSelector || it.parent is KtUserType || it.isOperationReference || it is KtBlockExpression) return@Condition false
|
||||
|
||||
// Both KtLambdaExpression and KtFunctionLiteral have the same offset, so we add only one of them -> KtLambdaExpression
|
||||
if (it is KtFunctionLiteral) return@Condition false
|
||||
|
||||
if (statementsOnly && it.parent !is KtBlockExpression) return@Condition false
|
||||
if (checkCanBeUsedAsValue && !it.canBeUsedAsValue()) return@Condition false
|
||||
|
||||
typePredicate == null || it.getType(it.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION))?.let { typePredicate(it) } ?: false
|
||||
}
|
||||
|
||||
private fun KtExpression.canBeUsedAsValue() =
|
||||
!KtPsiUtil.isAssignment(this) &&
|
||||
!this.isNamedDeclaration &&
|
||||
this !is KtLoopExpression
|
||||
}
|
||||
|
||||
override fun getExpressions(context: PsiElement, document: Document, offset: Int): List<PsiElement> {
|
||||
val expressions = super.getExpressions(context, document, offset)
|
||||
|
||||
@@ -137,27 +157,24 @@ private class KtExpressionPostfixTemplateSelector(
|
||||
) = context.parentsWithSelf
|
||||
.filterIsInstance<KtExpression>()
|
||||
.takeWhile {
|
||||
it !is KtBlockExpression &&
|
||||
!it.isEffectivelyDeclaration()
|
||||
}.filter {
|
||||
!it.isSelector &&
|
||||
it.parent !is KtUserType &&
|
||||
!it.isOperationReference &&
|
||||
!KtPsiUtil.isAssignment(it) &&
|
||||
// Both KtLambdaExpression and KtFunctionLiteral have the same offset, so we add only one of them -> KtLambdaExpression
|
||||
it !is KtFunctionLiteral
|
||||
!it.isBlockBodyInDeclaration
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private val KtExpression.isOperationReference: Boolean
|
||||
get() = this.node.elementType == KtNodeTypes.OPERATION_REFERENCE
|
||||
|
||||
private fun KtElement.isEffectivelyDeclaration() =
|
||||
this is KtNamedDeclaration &&
|
||||
// function literal is an expression while it's also a subtype of KtNamedDeclaration
|
||||
this !is KtFunctionLiteral &&
|
||||
// !(fun (a) = 1)
|
||||
(this !is KtNamedFunction || this.name == null)
|
||||
private val KtElement.isBlockBodyInDeclaration: Boolean
|
||||
get() = this is KtBlockExpression && (parent as? KtElement)?.isNamedDeclarationWithBody == true
|
||||
|
||||
private val KtElement.isNamedDeclaration: Boolean
|
||||
get() = this is KtNamedDeclaration && !isAnonymousFunction
|
||||
|
||||
private val KtElement.isNamedDeclarationWithBody: Boolean
|
||||
get() = this is KtDeclarationWithBody && !isAnonymousFunction
|
||||
|
||||
private val KtDeclaration.isAnonymousFunction: Boolean
|
||||
get() = this is KtFunctionLiteral || (this is KtNamedFunction && this.name == null)
|
||||
|
||||
private val KtExpression.isSelector: Boolean
|
||||
get() = parent is KtQualifiedExpression && (parent as KtQualifiedExpression).selectorExpression == this
|
||||
|
||||
+4
-4
@@ -46,7 +46,7 @@ internal class KtForEachPostfixTemplate(
|
||||
name,
|
||||
"for (item in expr)",
|
||||
"for (\$name$ in \$expr$) {\n \$END$\n}",
|
||||
createExpressionSelector(statementsOnly = true, predicate = KotlinType::containsIteratorMethod)
|
||||
createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::containsIteratorMethod)
|
||||
) {
|
||||
override fun setVariables(template: Template, element: PsiElement) {
|
||||
val name = MacroCallNode(SuggestVariableNameMacro())
|
||||
@@ -63,13 +63,13 @@ internal object KtAssertPostfixTemplate : ConstantStringBasedPostfixTemplate(
|
||||
"assert",
|
||||
"assert(expr) { \"\" }",
|
||||
"assert(\$expr$) { \"\$END$\" }",
|
||||
createExpressionSelector(statementsOnly = true, predicate = KotlinType::isBoolean)
|
||||
createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::isBoolean)
|
||||
)
|
||||
|
||||
internal object KtParenthesizedPostfixTemplate : ConstantStringBasedPostfixTemplate(
|
||||
"par", "(expr)",
|
||||
"(\$expr$)\$END$",
|
||||
createExpressionSelector(statementsOnly = false)
|
||||
createExpressionSelector()
|
||||
)
|
||||
|
||||
internal object KtSoutPostfixTemplate : ConstantStringBasedPostfixTemplate(
|
||||
@@ -90,5 +90,5 @@ internal object KtWhilePostfixTemplate : ConstantStringBasedPostfixTemplate(
|
||||
"while",
|
||||
"while (expr) {}",
|
||||
"while (\$expr$) {\n\$END$\n}",
|
||||
createExpressionSelector(statementsOnly = true, predicate = KotlinType::isBoolean)
|
||||
createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::isBoolean)
|
||||
)
|
||||
|
||||
+11
-6
@@ -29,14 +29,14 @@ import org.jetbrains.kotlin.types.typeUtil.isBoolean
|
||||
|
||||
internal object KtIfExpressionPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
"if", "if (expr)",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) { it.isBoolean() }
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector { it.isBoolean() }
|
||||
) {
|
||||
override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false)
|
||||
}
|
||||
|
||||
internal object KtElseExpressionPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
"else", "if (!expr)",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) { it.isBoolean() }
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector { it.isBoolean() }
|
||||
) {
|
||||
override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false)
|
||||
override fun getWrappedExpression(expression: PsiElement?) = (expression as KtExpression).negate()
|
||||
@@ -44,7 +44,7 @@ internal object KtElseExpressionPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
|
||||
internal class KtNotNullPostfixTemplate(val name: String) : SurroundPostfixTemplateBase(
|
||||
name, "if (expr != null)",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false, predicate = TypeUtils::isNullableType)
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(typePredicate = TypeUtils::isNullableType)
|
||||
) {
|
||||
override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false)
|
||||
override fun getTail() = "!= null"
|
||||
@@ -52,7 +52,7 @@ internal class KtNotNullPostfixTemplate(val name: String) : SurroundPostfixTempl
|
||||
|
||||
internal object KtIsNullPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
"null", "if (expr == null)",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false, predicate = TypeUtils::isNullableType)
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(typePredicate = TypeUtils::isNullableType)
|
||||
) {
|
||||
override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false)
|
||||
override fun getTail() = "== null"
|
||||
@@ -60,14 +60,19 @@ internal object KtIsNullPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
|
||||
internal object KtWhenExpressionPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
"when", "when (expr)",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false)
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector()
|
||||
) {
|
||||
override fun getSurrounder() = KotlinWhenSurrounder()
|
||||
}
|
||||
|
||||
internal object KtTryPostfixTemplate : SurroundPostfixTemplateBase(
|
||||
"try", "try { code } catch (e: Exception) { }",
|
||||
KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false)
|
||||
KtPostfixTemplatePsiInfo,
|
||||
createExpressionSelector(
|
||||
checkCanBeUsedAsValue = false,
|
||||
// Do not suggest 'val x = try { init } catch (e: Exception) { }'
|
||||
statementsOnly = true
|
||||
)
|
||||
) {
|
||||
override fun getSurrounder() = KotlinTryCatchSurrounder()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(x: Boolean) {
|
||||
foo(x.assert<caret>)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(x: Boolean) {
|
||||
foo(x.assert <caret>)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
for (i in 1..100) {
|
||||
|
||||
}.par<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
for (i in 1..100) {
|
||||
|
||||
}.par <caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
for (i in 1..100) {
|
||||
}.try<caret>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun foo() {
|
||||
try {
|
||||
for (i in 1..100) {
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val x = 1.try<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo() {
|
||||
try {
|
||||
val x = 1
|
||||
} catch(e: Exception) {
|
||||
}
|
||||
}
|
||||
+24
@@ -41,6 +41,12 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("assertNonTopLevel.kt")
|
||||
public void testAssertNonTopLevel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/assertNonTopLevel.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("doNotProposeWrappingIncrement.kt")
|
||||
public void testDoNotProposeWrappingIncrement() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/doNotProposeWrappingIncrement.kt");
|
||||
@@ -71,6 +77,12 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noParOnLoop.kt")
|
||||
public void testNoParOnLoop() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/noParOnLoop.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noReturnForNonStatement.kt")
|
||||
public void testNoReturnForNonStatement() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/noReturnForNonStatement.kt");
|
||||
@@ -137,6 +149,18 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("tryOnFor.kt")
|
||||
public void testTryOnFor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/tryOnFor.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("tryOnStatement.kt")
|
||||
public void testTryOnStatement() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/tryOnStatement.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("val.kt")
|
||||
public void testVal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/val.kt");
|
||||
|
||||
Reference in New Issue
Block a user