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?
= 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 org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getOutermostParenthesizerOrThis
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.KotlinType
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> {
val result = LinkedHashSet<String>()
result.addNamesByExpression(expression, validator)
result.addNamesByExpression(expression, bindingContext, validator)
val type = bindingContext.getType(expression)
if (type != null) {
@@ -62,10 +67,13 @@ public object KotlinNameSuggester {
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>()
result.addNamesByExpression(expression, validator)
result.addNamesByExpression(expression, bindingContext, validator)
if (result.isEmpty()) {
result.addName(defaultName, validator)
@@ -74,10 +82,14 @@ public object KotlinNameSuggester {
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>()
suggestNamesByExpressionOnly(collection, { true })
suggestNamesByExpressionOnly(collection, bindingContext, { true })
.map { StringUtil.unpluralize(it) }
.filterNotNull()
.mapTo(result) { suggestNameByName(it, validator) }
@@ -261,18 +273,41 @@ public object KotlinNameSuggester {
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
val expression = KtPsiUtil.safeDeparenthesize(expression)
when (expression) {
is KtSimpleNameExpression -> addCamelNames(expression.getReferencedName(), validator)
addNamesByValueArgument(expression, bindingContext, validator)
addNamesByExpressionPSI(expression, validator)
}
is KtQualifiedExpression -> addNamesByExpression(expression.getSelectorExpression(), validator)
is KtCallExpression -> addNamesByExpression(expression.getCalleeExpression(), validator)
is KtPostfixExpression -> addNamesByExpression(expression.getBaseExpression(), validator)
private fun MutableCollection<String>.addNamesByValueArgument(
expression: KtExpression,
bindingContext: BindingContext?,
validator: (String) -> Boolean
) {
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.openapi.editor.Editor
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.core.*
import org.jetbrains.kotlin.idea.resolve.ideService
@@ -58,7 +59,8 @@ public class IterateExpressionIntention : SelfTargetingIntention<KtExpression>(j
val elementType = data(element)!!.elementType
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
forExpression = element.replaced(forExpression)
@@ -801,7 +801,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
builder.replaceElement(parameterTypeRef, parameterTypeExpression)
// add parameter name to the template
val possibleNamesFromExpression = parameter.typeInfo.possibleNamesFromExpression
val possibleNamesFromExpression = parameter.typeInfo.getPossibleNamesFromExpression(currentFileContext)
val preferredName = parameter.preferredName
val possibleNames = if (preferredName != null) {
arrayOf(preferredName, *possibleNamesFromExpression)
@@ -41,8 +41,8 @@ abstract class TypeInfo(val variance: Variance) {
}
class ByExpression(val expression: KtExpression, variance: Variance): TypeInfo(variance) {
override val possibleNamesFromExpression: Array<String> by lazy {
KotlinNameSuggester.suggestNamesByExpressionOnly(expression, { true }).toTypedArray()
override fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array<String> {
return KotlinNameSuggester.suggestNamesByExpressionOnly(expression, bindingContext, { true }).toTypedArray()
}
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) {
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)
}
@@ -84,7 +84,7 @@ abstract class TypeInfo(val variance: Variance) {
open val substitutionsAllowed: Boolean = true
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>
protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List<KotlinType> {
@@ -652,7 +652,7 @@ private class ConstructedExpressionWrapperWithIntroduceFeature(
val name = if (nameSuggestion != null)
KotlinNameSuggester.suggestNameByName(nameSuggestion, validator)
else
KotlinNameSuggester.suggestNamesByExpressionOnly(value, validator, "t").first()
KotlinNameSuggester.suggestNamesByExpressionOnly(value, bindingContext, validator, "t").first()
return Name.identifier(name)
}
@@ -3,7 +3,7 @@
class A<T>(val n: T) {
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.
}
}
@@ -3,7 +3,7 @@
class A<T>(val n: T) {
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.
}
}
@@ -9,12 +9,12 @@ class A(val a: Int) {
fun test() {
val a = A(1)
val a3 = A(2)
a.foo(a.a + a3.a)
val i2 = A(2)
a.foo(a.a + i2.a)
with(A(1)) {
val a2 = A(2)
foo(this.a + a2.a)
val a1 = A(2)
this.foo(this.a + a1.a)
val i1 = A(2)
foo(this.a + i1.a)
val i = A(2)
this.foo(this.a + i.a)
}
}
@@ -9,12 +9,12 @@ class A(val a: Int) {
fun test() {
val a = A(1)
val a3 = A(2)
a.foo(a.a + a3.a)
val i2 = A(2)
a.foo(a.a + i2.a)
with(A(1)) {
val a2 = A(2)
foo(this.a + a2.a)
val a1 = A(2)
this.foo(this.a + a1.a)
val i1 = A(2)
foo(this.a + i1.a)
val i = A(2)
this.foo(this.a + i.a)
}
}
@@ -1,6 +1,6 @@
open class A(n: Int)
fun foo() {
val i = 1
val x = object : A(i) {}
val n = 1
val x = object : A(n) {}
}
@@ -1,5 +1,5 @@
// WITH_RUNTIME
fun foo(c : Collection<String>){
val function: (String) -> Boolean = { it; false }
c.filter(function)
val predicate: (String) -> Boolean = { it; false }
c.filter(predicate)
}
@@ -3,6 +3,6 @@ class A {
}
fun apply(x: (A) -> Int) = 2
fun test() {
val function: (A) -> Int = { it.foo() }
apply(function)
val x: (A) -> Int = { it.foo() }
apply(x)
}
@@ -1,5 +1,5 @@
// WITH_RUNTIME
fun foo(c : Collection<String>){
val function: (String) -> Boolean = { it.length() > 1 }
c.filterTo(ArrayList<String>(), function)
val predicate: (String) -> Boolean = { it.length() > 1 }
c.filterTo(ArrayList<String>(), predicate)
}
@@ -2,7 +2,7 @@
fun foo(p: Int, list: List<Int>) {
if (p > 0) {
val function: (Int) -> Boolean = { it > 0 }
list.filter(function)
val predicate: (Int) -> Boolean = { it > 0 }
list.filter(predicate)
}
}
@@ -1,9 +1,9 @@
// WITH_RUNTIME
fun main(args: Array<String>) {
with(A()) {
val prop1 = prop
println(prop1)
println(prop1)
val message = prop
println(message)
println(message)
}
}
@@ -1,5 +1,5 @@
fun a(x: Int) {}
fun b() {
val i = 1
a(i)
val x = 1
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.core.KotlinNameSuggester
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.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
@@ -56,6 +58,10 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
public fun testURL() { doTest() }
public fun testParameterNameByArgumentExpression() { doTest() }
public fun testParameterNameByParenthesizedArgumentExpression() { doTest() }
override fun setUp() {
super.setUp()
myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/refactoring/nameSuggester")
@@ -65,7 +71,11 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
myFixture.configureByFile(getTestName(false) + ".kt")
val file = myFixture.getFile() as KtFile
val expectedResultText = KotlinTestUtils.getLastCommentInFile(file)
val withRuntime = InTextDirectivesUtils.isDirectiveDefined(file.text, "//WITH_RUNTIME")
try {
if (withRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
KotlinRefactoringUtil.selectExpression(myFixture.getEditor(), file, object : KotlinRefactoringUtil.SelectExpressionCallback {
override fun run(expression: KtExpression?) {
val names = KotlinNameSuggester.suggestNamesByExpressionAndType(expression!!, expression.analyze(BodyResolveMode.PARTIAL), { true }, "value").sorted()
@@ -77,5 +87,10 @@ public class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
catch (e: KotlinRefactoringUtil.IntroduceRefactoringException) {
throw AssertionError("Failed to find expression: " + e.getMessage())
}
finally {
if (withRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
}
}