KT-35648 RemoveArgumentNameIntention: Support MixedNamedArgumentsInTheirOwnPosition

- ^KT-35648 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-12-28 15:38:05 +09:00
committed by Roman Golyshev
parent 401baa35ab
commit 2daedc98b5
10 changed files with 102 additions and 40 deletions
@@ -34,17 +34,11 @@ import java.util.*
object NamedArgumentCompletion { object NamedArgumentCompletion {
fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression, resolutionFacade: ResolutionFacade): Boolean { fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression, resolutionFacade: ResolutionFacade): Boolean {
val thisArgument = nameExpression.parent as? KtValueArgument ?: return false val thisArgument = nameExpression.parent as? KtValueArgument ?: return false
if (thisArgument.isNamed()) return false if (thisArgument.isNamed()) return false
val resolvedCall = thisArgument.getStrictParentOfType<KtCallElement>()?.resolveToCall(resolutionFacade) ?: return false
val callElement = thisArgument.getStrictParentOfType<KtCallElement>() ?: return false return !thisArgument.canBeUsedWithoutNameInCall(resolvedCall)
val argumentsBeforeThis = callElement.valueArguments.takeWhile { it != thisArgument }
if (!nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)) {
return argumentsBeforeThis.any { it.isNamed() }
}
val resolvedCall = callElement.resolveToCall(resolutionFacade) ?: return false
return argumentsBeforeThis.any { it.isNamed() && !it.placedOnItsOwnPositionInCall(resolvedCall) }
} }
fun complete(collector: LookupElementsCollector, expectedInfos: Collection<ExpectedInfo>, callType: CallType<*>) { fun complete(collector: LookupElementsCollector, expectedInfos: Collection<ExpectedInfo>, callType: CallType<*>) {
@@ -83,6 +77,18 @@ object NamedArgumentCompletion {
} }
} }
/**
* Checks whether argument in the [resolvedCall] can be used without its name (as positional argument).
*/
fun KtValueArgument.canBeUsedWithoutNameInCall(resolvedCall: ResolvedCall<out CallableDescriptor>): Boolean {
val argumentsBeforeThis = resolvedCall.call.valueArguments.takeWhile { it != this }
return if (languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)) {
argumentsBeforeThis.none { it.isNamed() && !it.placedOnItsOwnPositionInCall(resolvedCall) }
} else {
argumentsBeforeThis.none { it.isNamed() }
}
}
/** /**
* Checks whether argument in the [resolvedCall] is on the same position as it listed in the callable definition. * Checks whether argument in the [resolvedCall] is on the same position as it listed in the callable definition.
* *
@@ -99,6 +105,6 @@ object NamedArgumentCompletion {
* ) * )
* ``` * ```
*/ */
private fun ValueArgument.placedOnItsOwnPositionInCall(resolvedCall: ResolvedCall<out CallableDescriptor>): Boolean { fun ValueArgument.placedOnItsOwnPositionInCall(resolvedCall: ResolvedCall<out CallableDescriptor>): Boolean {
return resolvedCall.getParameterForArgument(this)?.index == resolvedCall.call.valueArguments.indexOf(this) return resolvedCall.getParameterForArgument(this)?.index == resolvedCall.call.valueArguments.indexOf(this)
} }
@@ -1,17 +1,6 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
@@ -19,33 +8,32 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.completion.canBeUsedWithoutNameInCall
import org.jetbrains.kotlin.idea.completion.placedOnItsOwnPositionInCall
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
import org.jetbrains.kotlin.resolve.calls.components.isVararg import org.jetbrains.kotlin.resolve.calls.components.isVararg
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
class RemoveArgumentNameIntention : SelfTargetingRangeIntention<KtValueArgument>(KtValueArgument::class.java, "Remove argument name") { class RemoveArgumentNameIntention : SelfTargetingRangeIntention<KtValueArgument>(KtValueArgument::class.java, "Remove argument name") {
override fun applicabilityRange(element: KtValueArgument): TextRange? { override fun applicabilityRange(element: KtValueArgument): TextRange? {
if (!element.isNamed()) return null if (!element.isNamed()) return null
val resolvedCall = element.getStrictParentOfType<KtCallElement>()?.resolveToCall() ?: return null
val argumentList = element.parent as? KtValueArgumentList ?: return null if (!element.placedOnItsOwnPositionInCall(resolvedCall)) return null
val arguments = argumentList.arguments if (!element.canBeUsedWithoutNameInCall(resolvedCall)) return null
if (arguments.asSequence().takeWhile { it != element }.any { it.isNamed() }) return null
val callExpr = argumentList.parent as? KtCallElement ?: return null val argumentExpression = element.getArgumentExpression() ?: return null
val argumentMatch = callExpr.resolveToArgumentMatch(element) ?: return null return TextRange(element.startOffset, argumentExpression.startOffset)
if (argumentMatch.valueParameter.index != arguments.indexOf(element)) return null
val expression = element.getArgumentExpression() ?: return null
return TextRange(element.startOffset, expression.startOffset)
} }
override fun applyTo(element: KtValueArgument, editor: Editor?) { override fun applyTo(element: KtValueArgument, editor: Editor?) {
val argumentExpr = element.getArgumentExpression() ?: return val argumentExpr = element.getArgumentExpression() ?: return
val argumentList = element.parent as? KtValueArgumentList ?: return val argumentList = element.parent as? KtValueArgumentList ?: return
val callExpr = argumentList.parent as? KtCallElement ?: return val resolvedCall = (argumentList.parent as? KtCallElement)?.resolveToCall() ?: return
val psiFactory = KtPsiFactory(element) val psiFactory = KtPsiFactory(element)
if (argumentExpr is KtCollectionLiteralExpression && callExpr.resolveToArgumentMatch(element)?.valueParameter?.isVararg == true) { if (argumentExpr is KtCollectionLiteralExpression && resolvedCall.getParameterForArgument(element)?.isVararg == true) {
argumentExpr.getInnerExpressions() argumentExpr.getInnerExpressions()
.map { psiFactory.createArgument(it) } .map { psiFactory.createArgument(it) }
.reversed() .reversed()
@@ -56,8 +44,4 @@ class RemoveArgumentNameIntention : SelfTargetingRangeIntention<KtValueArgument>
element.replace(newArgument) element.replace(newArgument)
} }
} }
private fun KtCallElement.resolveToArgumentMatch(element: KtValueArgument): ArgumentMatch? {
return resolveToCall()?.getArgumentMapping(element) as? ArgumentMatch ?: return null
}
} }
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
fun foo(s: String, b: Boolean) {}
fun bar() {
foo(s = "", <caret>b = true)
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
fun foo(s: String, b: Boolean) {}
fun bar() {
foo(s = "", true)
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
fun foo(name1: Int, name2: Int, name3: Int) {}
fun usage() {
foo(1, name2 = 2, <caret>name3 = 3)
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
fun foo(name1: Int, name2: Int, name3: Int) {}
fun usage() {
foo(1, name2 = 2, 3)
}
@@ -0,0 +1,7 @@
// COMPILER_ARGUMENTS: +XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
// IS_APPLICABLE: false
fun foo(name1: Int, name2: Int, name3: Int) {}
fun usage() {
foo(name2 = 2, <caret>name1 = 1, name3 = 3)
}
@@ -0,0 +1,7 @@
// COMPILER_ARGUMENTS: +XXLanguage:+MixedNamedArgumentsInTheirOwnPosition
// IS_APPLICABLE: false
fun foo(name1: Int, name2: Int, name3: Int) {}
fun usage() {
foo(name2 = 2, name1 = 1, <caret>name3 = 3)
}
@@ -1,5 +1,6 @@
// COMPILER_ARGUMENTS: -XXLanguage:-MixedNamedArgumentsInTheirOwnPosition
// IS_APPLICABLE: false // IS_APPLICABLE: false
fun foo(s: String, b: Boolean){} fun foo(s: String, b: Boolean) {}
fun bar() { fun bar() {
foo(s = "", <caret>b = true) foo(s = "", <caret>b = true)
@@ -12711,6 +12711,39 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
public void testSuperClassConstructor() throws Exception { public void testSuperClassConstructor() throws Exception {
runTest("idea/testData/intentions/removeArgumentName/superClassConstructor.kt"); runTest("idea/testData/intentions/removeArgumentName/superClassConstructor.kt");
} }
@TestMetadata("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class MixedNamedArgumentsInTheirOwnPosition extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInMixedNamedArgumentsInTheirOwnPosition() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("namedArgumentBefore.kt")
public void testNamedArgumentBefore() throws Exception {
runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt");
}
@TestMetadata("namedArgumentBefore2.kt")
public void testNamedArgumentBefore2() throws Exception {
runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt");
}
@TestMetadata("notInPosition.kt")
public void testNotInPosition() throws Exception {
runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt");
}
@TestMetadata("notInPosition2.kt")
public void testNotInPosition2() throws Exception {
runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt");
}
}
} }
@TestMetadata("idea/testData/intentions/removeBraces") @TestMetadata("idea/testData/intentions/removeBraces")