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:
Denis Zharkov
2016-09-30 11:30:48 +03:00
parent ba2d2a036e
commit 3c24996073
12 changed files with 121 additions and 39 deletions
@@ -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
@@ -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)
)
@@ -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>)
}
+5
View File
@@ -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>
}
+4
View File
@@ -0,0 +1,4 @@
fun foo() {
for (i in 1..100) {
}.try<caret>
}
+7
View File
@@ -0,0 +1,7 @@
fun foo() {
try {
for (i in 1..100) {
}
} catch(e: Exception) {
}
}
+3
View File
@@ -0,0 +1,3 @@
fun foo() {
val x = 1.try<caret>
}
@@ -0,0 +1,6 @@
fun foo() {
try {
val x = 1
} catch(e: Exception) {
}
}
@@ -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");