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 {
fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression, resolutionFacade: ResolutionFacade): Boolean {
val thisArgument = nameExpression.parent as? KtValueArgument ?: return false
if (thisArgument.isNamed()) return false
val resolvedCall = thisArgument.getStrictParentOfType<KtCallElement>()?.resolveToCall(resolutionFacade) ?: return false
val callElement = thisArgument.getStrictParentOfType<KtCallElement>() ?: return false
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) }
return !thisArgument.canBeUsedWithoutNameInCall(resolvedCall)
}
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.
*
@@ -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)
}
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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.
* 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.
*/
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.util.TextRange
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.psiUtil.getStrictParentOfType
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.model.ArgumentMatch
class RemoveArgumentNameIntention : SelfTargetingRangeIntention<KtValueArgument>(KtValueArgument::class.java, "Remove argument name") {
override fun applicabilityRange(element: KtValueArgument): TextRange? {
if (!element.isNamed()) return null
val resolvedCall = element.getStrictParentOfType<KtCallElement>()?.resolveToCall() ?: return null
val argumentList = element.parent as? KtValueArgumentList ?: return null
val arguments = argumentList.arguments
if (arguments.asSequence().takeWhile { it != element }.any { it.isNamed() }) return null
if (!element.placedOnItsOwnPositionInCall(resolvedCall)) return null
if (!element.canBeUsedWithoutNameInCall(resolvedCall)) return null
val callExpr = argumentList.parent as? KtCallElement ?: return null
val argumentMatch = callExpr.resolveToArgumentMatch(element) ?: return null
if (argumentMatch.valueParameter.index != arguments.indexOf(element)) return null
val expression = element.getArgumentExpression() ?: return null
return TextRange(element.startOffset, expression.startOffset)
val argumentExpression = element.getArgumentExpression() ?: return null
return TextRange(element.startOffset, argumentExpression.startOffset)
}
override fun applyTo(element: KtValueArgument, editor: Editor?) {
val argumentExpr = element.getArgumentExpression() ?: 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)
if (argumentExpr is KtCollectionLiteralExpression && callExpr.resolveToArgumentMatch(element)?.valueParameter?.isVararg == true) {
if (argumentExpr is KtCollectionLiteralExpression && resolvedCall.getParameterForArgument(element)?.isVararg == true) {
argumentExpr.getInnerExpressions()
.map { psiFactory.createArgument(it) }
.reversed()
@@ -56,8 +44,4 @@ class RemoveArgumentNameIntention : SelfTargetingRangeIntention<KtValueArgument>
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
fun foo(s: String, b: Boolean){}
fun foo(s: String, b: Boolean) {}
fun bar() {
foo(s = "", <caret>b = true)
@@ -12711,6 +12711,39 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
public void testSuperClassConstructor() throws Exception {
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")