Name Suggester: Suggest parameter name for the corresponding argument expression

#KT-8111 Fixed
This commit is contained in:
Alexey Sedunov
2015-11-10 15:07:22 +03:00
parent f21603daf2
commit aeee8bafe6
20 changed files with 137 additions and 51 deletions
@@ -406,4 +406,16 @@ public fun KtDeclaration.visibilityModifier(): PsiElement? {
public fun KtDeclaration.visibilityModifierType(): KtModifierKeywordToken? public fun KtDeclaration.visibilityModifierType(): KtModifierKeywordToken?
= visibilityModifier()?.node?.elementType as KtModifierKeywordToken? = visibilityModifier()?.node?.elementType as KtModifierKeywordToken?
public fun KtStringTemplateExpression.isPlain() = entries.all { it is KtLiteralStringTemplateEntry } public fun KtStringTemplateExpression.isPlain() = entries.all { it is KtLiteralStringTemplateEntry }
public fun KtExpression.getOutermostParenthesizerOrThis(): KtExpression {
return (parentsWithSelf zip parents).firstOrNull {
val (element, parent) = it
when (parent) {
is KtParenthesizedExpression -> false
is KtAnnotatedExpression -> parent.baseExpression != element
is KtLabeledExpression -> parent.baseExpression != element
else -> true
}
}?.first as KtExpression? ?: this
}
@@ -18,10 +18,15 @@ package org.jetbrains.kotlin.idea.core
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.KotlinLexer import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getOutermostParenthesizerOrThis
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
@@ -36,7 +41,7 @@ public object KotlinNameSuggester {
public fun suggestNamesByExpressionAndType(expression: KtExpression, bindingContext: BindingContext, validator: (String) -> Boolean, defaultName: String?): Collection<String> { public fun suggestNamesByExpressionAndType(expression: KtExpression, bindingContext: BindingContext, validator: (String) -> Boolean, defaultName: String?): Collection<String> {
val result = LinkedHashSet<String>() val result = LinkedHashSet<String>()
result.addNamesByExpression(expression, validator) result.addNamesByExpression(expression, bindingContext, validator)
val type = bindingContext.getType(expression) val type = bindingContext.getType(expression)
if (type != null) { if (type != null) {
@@ -62,10 +67,13 @@ public object KotlinNameSuggester {
return result return result
} }
public fun suggestNamesByExpressionOnly(expression: KtExpression, validator: (String) -> Boolean, defaultName: String? = null): List<String> { public fun suggestNamesByExpressionOnly(
expression: KtExpression,
bindingContext: BindingContext?,
validator: (String) -> Boolean, defaultName: String? = null): List<String> {
val result = ArrayList<String>() val result = ArrayList<String>()
result.addNamesByExpression(expression, validator) result.addNamesByExpression(expression, bindingContext, validator)
if (result.isEmpty()) { if (result.isEmpty()) {
result.addName(defaultName, validator) result.addName(defaultName, validator)
@@ -74,10 +82,14 @@ public object KotlinNameSuggester {
return result return result
} }
public fun suggestIterationVariableNames(collection: KtExpression, elementType: KotlinType, validator: (String) -> Boolean, defaultName: String?): Collection<String> { public fun suggestIterationVariableNames(
collection: KtExpression,
elementType: KotlinType,
bindingContext: BindingContext?,
validator: (String) -> Boolean, defaultName: String?): Collection<String> {
val result = LinkedHashSet<String>() val result = LinkedHashSet<String>()
suggestNamesByExpressionOnly(collection, { true }) suggestNamesByExpressionOnly(collection, bindingContext, { true })
.map { StringUtil.unpluralize(it) } .map { StringUtil.unpluralize(it) }
.filterNotNull() .filterNotNull()
.mapTo(result) { suggestNameByName(it, validator) } .mapTo(result) { suggestNameByName(it, validator) }
@@ -261,18 +273,41 @@ public object KotlinNameSuggester {
return matcher.replaceAll("") return matcher.replaceAll("")
} }
private fun MutableCollection<String>.addNamesByExpression(expression: KtExpression?, validator: (String) -> Boolean) { private fun MutableCollection<String>.addNamesByExpressionPSI(expression: KtExpression?, validator: (String) -> Boolean) {
if (expression == null) return
val deparenthesized = KtPsiUtil.safeDeparenthesize(expression)
when (deparenthesized) {
is KtSimpleNameExpression -> addCamelNames(deparenthesized.getReferencedName(), validator)
is KtQualifiedExpression -> addNamesByExpressionPSI(deparenthesized.selectorExpression, validator)
is KtCallExpression -> addNamesByExpressionPSI(deparenthesized.calleeExpression, validator)
is KtPostfixExpression -> addNamesByExpressionPSI(deparenthesized.baseExpression, validator)
}
}
private fun MutableCollection<String>.addNamesByExpression(
expression: KtExpression?,
bindingContext: BindingContext?,
validator: (String) -> Boolean
) {
if (expression == null) return if (expression == null) return
val expression = KtPsiUtil.safeDeparenthesize(expression) addNamesByValueArgument(expression, bindingContext, validator)
when (expression) { addNamesByExpressionPSI(expression, validator)
is KtSimpleNameExpression -> addCamelNames(expression.getReferencedName(), validator) }
is KtQualifiedExpression -> addNamesByExpression(expression.getSelectorExpression(), validator) private fun MutableCollection<String>.addNamesByValueArgument(
expression: KtExpression,
is KtCallExpression -> addNamesByExpression(expression.getCalleeExpression(), validator) bindingContext: BindingContext?,
validator: (String) -> Boolean
is KtPostfixExpression -> addNamesByExpression(expression.getBaseExpression(), validator) ) {
if (bindingContext == null) return
val argumentExpression = expression.getOutermostParenthesizerOrThis()
val valueArgument = argumentExpression.parent as? KtValueArgument ?: return
val resolvedCall = argumentExpression.getParentResolvedCall(bindingContext) ?: return
val argumentMatch = resolvedCall.getArgumentMapping(valueArgument) as? ArgumentMatch ?: return
val parameter = argumentMatch.valueParameter
if (parameter.containingDeclaration.hasStableParameterNames()) {
addName(parameter.name.asString(), validator)
} }
} }
@@ -21,6 +21,7 @@ import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.impl.ConstantNode import com.intellij.codeInsight.template.impl.ConstantNode
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.* import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.resolve.ideService import org.jetbrains.kotlin.idea.resolve.ideService
@@ -58,7 +59,8 @@ public class IterateExpressionIntention : SelfTargetingIntention<KtExpression>(j
val elementType = data(element)!!.elementType val elementType = data(element)!!.elementType
val nameValidator = NewDeclarationNameValidator(element, element.siblings(), NewDeclarationNameValidator.Target.VARIABLES) val nameValidator = NewDeclarationNameValidator(element, element.siblings(), NewDeclarationNameValidator.Target.VARIABLES)
val names = KotlinNameSuggester.suggestIterationVariableNames(element, elementType, nameValidator, "e") val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val names = KotlinNameSuggester.suggestIterationVariableNames(element, elementType, bindingContext, nameValidator, "e")
var forExpression = KtPsiFactory(element).createExpressionByPattern("for($0 in $1) {\nx\n}", names.first(), element) as KtForExpression var forExpression = KtPsiFactory(element).createExpressionByPattern("for($0 in $1) {\nx\n}", names.first(), element) as KtForExpression
forExpression = element.replaced(forExpression) forExpression = element.replaced(forExpression)
@@ -801,7 +801,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
builder.replaceElement(parameterTypeRef, parameterTypeExpression) builder.replaceElement(parameterTypeRef, parameterTypeExpression)
// add parameter name to the template // add parameter name to the template
val possibleNamesFromExpression = parameter.typeInfo.possibleNamesFromExpression val possibleNamesFromExpression = parameter.typeInfo.getPossibleNamesFromExpression(currentFileContext)
val preferredName = parameter.preferredName val preferredName = parameter.preferredName
val possibleNames = if (preferredName != null) { val possibleNames = if (preferredName != null) {
arrayOf(preferredName, *possibleNamesFromExpression) arrayOf(preferredName, *possibleNamesFromExpression)
@@ -41,8 +41,8 @@ abstract class TypeInfo(val variance: Variance) {
} }
class ByExpression(val expression: KtExpression, variance: Variance): TypeInfo(variance) { class ByExpression(val expression: KtExpression, variance: Variance): TypeInfo(variance) {
override val possibleNamesFromExpression: Array<String> by lazy { override fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array<String> {
KotlinNameSuggester.suggestNamesByExpressionOnly(expression, { true }).toTypedArray() return KotlinNameSuggester.suggestNamesByExpressionOnly(expression, bindingContext, { true }).toTypedArray()
} }
override fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> = override fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> =
@@ -70,7 +70,7 @@ abstract class TypeInfo(val variance: Variance) {
abstract class DelegatingTypeInfo(val delegate: TypeInfo): TypeInfo(delegate.variance) { abstract class DelegatingTypeInfo(val delegate: TypeInfo): TypeInfo(delegate.variance) {
override val substitutionsAllowed: Boolean = delegate.substitutionsAllowed override val substitutionsAllowed: Boolean = delegate.substitutionsAllowed
override val possibleNamesFromExpression: Array<String> get() = delegate.possibleNamesFromExpression override fun getPossibleNamesFromExpression(bindingContext: BindingContext) = delegate.getPossibleNamesFromExpression(bindingContext)
override fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> = delegate.getPossibleTypes(builder) override fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> = delegate.getPossibleTypes(builder)
} }
@@ -84,7 +84,7 @@ abstract class TypeInfo(val variance: Variance) {
open val substitutionsAllowed: Boolean = true open val substitutionsAllowed: Boolean = true
open val staticContextRequired: Boolean = false open val staticContextRequired: Boolean = false
open val possibleNamesFromExpression: Array<String> get() = ArrayUtil.EMPTY_STRING_ARRAY open fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array<String> = ArrayUtil.EMPTY_STRING_ARRAY
abstract fun getPossibleTypes(builder: CallableBuilder): List<KotlinType> abstract fun getPossibleTypes(builder: CallableBuilder): List<KotlinType>
protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List<KotlinType> { protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List<KotlinType> {
@@ -652,7 +652,7 @@ private class ConstructedExpressionWrapperWithIntroduceFeature(
val name = if (nameSuggestion != null) val name = if (nameSuggestion != null)
KotlinNameSuggester.suggestNameByName(nameSuggestion, validator) KotlinNameSuggester.suggestNameByName(nameSuggestion, validator)
else else
KotlinNameSuggester.suggestNamesByExpressionOnly(value, validator, "t").first() KotlinNameSuggester.suggestNamesByExpressionOnly(value, bindingContext, validator, "t").first()
return Name.identifier(name) return Name.identifier(name)
} }
@@ -3,7 +3,7 @@
class A<T>(val n: T) { class A<T>(val n: T) {
fun foo(a: Int): A<T> = throw Exception() fun foo(a: Int): A<T> = throw Exception()
fun foo(t: T, s: String): A<T> { fun foo(a: T, s: String): A<T> {
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
} }
@@ -3,7 +3,7 @@
class A<T>(val n: T) { class A<T>(val n: T) {
fun foo(i: Int, s: String): A<T> = throw Exception() fun foo(i: Int, s: String): A<T> = throw Exception()
fun foo(t: T): A<T> { fun foo(i: T): A<T> {
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
} }
@@ -9,12 +9,12 @@ class A(val a: Int) {
fun test() { fun test() {
val a = A(1) val a = A(1)
val a3 = A(2) val i2 = A(2)
a.foo(a.a + a3.a) a.foo(a.a + i2.a)
with(A(1)) { with(A(1)) {
val a2 = A(2) val i1 = A(2)
foo(this.a + a2.a) foo(this.a + i1.a)
val a1 = A(2) val i = A(2)
this.foo(this.a + a1.a) this.foo(this.a + i.a)
} }
} }
@@ -9,12 +9,12 @@ class A(val a: Int) {
fun test() { fun test() {
val a = A(1) val a = A(1)
val a3 = A(2) val i2 = A(2)
a.foo(a.a + a3.a) a.foo(a.a + i2.a)
with(A(1)) { with(A(1)) {
val a2 = A(2) val i1 = A(2)
foo(this.a + a2.a) foo(this.a + i1.a)
val a1 = A(2) val i = A(2)
this.foo(this.a + a1.a) this.foo(this.a + i.a)
} }
} }
@@ -1,6 +1,6 @@
open class A(n: Int) open class A(n: Int)
fun foo() { fun foo() {
val i = 1 val n = 1
val x = object : A(i) {} val x = object : A(n) {}
} }
@@ -1,5 +1,5 @@
// WITH_RUNTIME // WITH_RUNTIME
fun foo(c : Collection<String>){ fun foo(c : Collection<String>){
val function: (String) -> Boolean = { it; false } val predicate: (String) -> Boolean = { it; false }
c.filter(function) c.filter(predicate)
} }
@@ -3,6 +3,6 @@ class A {
} }
fun apply(x: (A) -> Int) = 2 fun apply(x: (A) -> Int) = 2
fun test() { fun test() {
val function: (A) -> Int = { it.foo() } val x: (A) -> Int = { it.foo() }
apply(function) apply(x)
} }
@@ -1,5 +1,5 @@
// WITH_RUNTIME // WITH_RUNTIME
fun foo(c : Collection<String>){ fun foo(c : Collection<String>){
val function: (String) -> Boolean = { it.length() > 1 } val predicate: (String) -> Boolean = { it.length() > 1 }
c.filterTo(ArrayList<String>(), function) c.filterTo(ArrayList<String>(), predicate)
} }
@@ -2,7 +2,7 @@
fun foo(p: Int, list: List<Int>) { fun foo(p: Int, list: List<Int>) {
if (p > 0) { if (p > 0) {
val function: (Int) -> Boolean = { it > 0 } val predicate: (Int) -> Boolean = { it > 0 }
list.filter(function) list.filter(predicate)
} }
} }
@@ -1,9 +1,9 @@
// WITH_RUNTIME // WITH_RUNTIME
fun main(args: Array<String>) { fun main(args: Array<String>) {
with(A()) { with(A()) {
val prop1 = prop val message = prop
println(prop1) println(message)
println(prop1) println(message)
} }
} }
@@ -1,5 +1,5 @@
fun a(x: Int) {} fun a(x: Int) {}
fun b() { fun b() {
val i = 1 val x = 1
a(i) a(x)
} }
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun x(items : Sequence<String>) {}
fun y() {
x(<selection>sequenceOf("")</selection>)
}
/*
items
of
sequence
sequenceOf
*/
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun x(items : Sequence<String>) {}
fun y() {
x(<selection>foo@ (sequenceOf(""))</selection>)
}
/*
items
of
sequence
sequenceOf
*/
@@ -21,10 +21,12 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() { public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
@@ -56,6 +58,10 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
public fun testURL() { doTest() } public fun testURL() { doTest() }
public fun testParameterNameByArgumentExpression() { doTest() }
public fun testParameterNameByParenthesizedArgumentExpression() { doTest() }
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/refactoring/nameSuggester") myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/refactoring/nameSuggester")
@@ -65,7 +71,11 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
myFixture.configureByFile(getTestName(false) + ".kt") myFixture.configureByFile(getTestName(false) + ".kt")
val file = myFixture.getFile() as KtFile val file = myFixture.getFile() as KtFile
val expectedResultText = KotlinTestUtils.getLastCommentInFile(file) val expectedResultText = KotlinTestUtils.getLastCommentInFile(file)
val withRuntime = InTextDirectivesUtils.isDirectiveDefined(file.text, "//WITH_RUNTIME")
try { try {
if (withRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
KotlinRefactoringUtil.selectExpression(myFixture.getEditor(), file, object : KotlinRefactoringUtil.SelectExpressionCallback { KotlinRefactoringUtil.selectExpression(myFixture.getEditor(), file, object : KotlinRefactoringUtil.SelectExpressionCallback {
override fun run(expression: KtExpression?) { override fun run(expression: KtExpression?) {
val names = KotlinNameSuggester.suggestNamesByExpressionAndType(expression!!, expression.analyze(BodyResolveMode.PARTIAL), { true }, "value").sorted() val names = KotlinNameSuggester.suggestNamesByExpressionAndType(expression!!, expression.analyze(BodyResolveMode.PARTIAL), { true }, "value").sorted()
@@ -77,5 +87,10 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
catch (e: KotlinRefactoringUtil.IntroduceRefactoringException) { catch (e: KotlinRefactoringUtil.IntroduceRefactoringException) {
throw AssertionError("Failed to find expression: " + e.getMessage()) throw AssertionError("Failed to find expression: " + e.getMessage())
} }
finally {
if (withRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
} }
} }