Smaller availability range for add argument name intention

This commit is contained in:
Valentin Kipyatkov
2015-06-24 11:22:45 +02:00
parent 9d83510af5
commit 98c0c636b3
14 changed files with 71 additions and 19 deletions
@@ -216,6 +216,8 @@ public fun PsiElement.getStartOffsetIn(ancestor: PsiElement): Int {
return offset return offset
} }
public fun TextRange.containsInside(offset: Int): Boolean = getStartOffset() < offset && offset < getEndOffset()
public val PsiChildRange.textRange: TextRange? public val PsiChildRange.textRange: TextRange?
get() { get() {
if (isEmpty) return null if (isEmpty) return null
@@ -25,14 +25,14 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.psi.JetElement import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.psiUtil.containsInside
import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
public abstract class JetSelfTargetingIntention<TElement : JetElement>( public abstract class JetSelfTargetingIntention<TElement : JetElement>(
public val elementType: Class<TElement>, public val elementType: Class<TElement>,
private var text: String, private var text: String,
private val familyName: String = text, private val familyName: String = text
private val firstElementOfTypeOnly: Boolean = false
) : IntentionAction { ) : IntentionAction {
protected val defaultText: String = text protected val defaultText: String = text
@@ -65,16 +65,17 @@ public abstract class JetSelfTargetingIntention<TElement : JetElement>(
elementsToCheck += commonParent.parentsWithSelf elementsToCheck += commonParent.parentsWithSelf
} }
val elementsOfType = elementsToCheck.filterIsInstance(elementType) for (element in elementsToCheck) {
if (firstElementOfTypeOnly) { if (elementType.isInstance(element) && isApplicableTo(element as TElement, offset)) {
val candidate = elementsOfType.firstOrNull() ?: return null return element as TElement
return if (isApplicableTo(candidate, offset)) candidate else null }
} if (!allowCaretInsideElement(element) && element.getTextRange().containsInside(offset)) break
else {
return elementsOfType.firstOrNull { isApplicableTo(it, offset) }
} }
return null
} }
protected open fun allowCaretInsideElement(element: PsiElement): Boolean = true
final override fun isAvailable(project: Project, editor: Editor, file: PsiFile) final override fun isAvailable(project: Project, editor: Editor, file: PsiFile)
= getTarget(editor, file) != null = getTarget(editor, file) != null
@@ -91,9 +92,8 @@ public abstract class JetSelfTargetingIntention<TElement : JetElement>(
public abstract class JetSelfTargetingRangeIntention<TElement : JetElement>( public abstract class JetSelfTargetingRangeIntention<TElement : JetElement>(
elementType: Class<TElement>, elementType: Class<TElement>,
text: String, text: String,
familyName: String = text, familyName: String = text
firstElementOfTypeOnly: Boolean = false ) : JetSelfTargetingIntention<TElement>(elementType, text, familyName) {
) : JetSelfTargetingIntention<TElement>(elementType, text, familyName, firstElementOfTypeOnly) {
public abstract fun applicabilityRange(element: TElement): TextRange? public abstract fun applicabilityRange(element: TElement): TextRange?
@@ -106,9 +106,8 @@ public abstract class JetSelfTargetingRangeIntention<TElement : JetElement>(
public abstract class JetSelfTargetingOffsetIndependentIntention<TElement : JetElement>( public abstract class JetSelfTargetingOffsetIndependentIntention<TElement : JetElement>(
elementType: Class<TElement>, elementType: Class<TElement>,
text: String, text: String,
familyName: String = text, familyName: String = text
firstElementOfTypeOnly: Boolean = false ) : JetSelfTargetingRangeIntention<TElement>(elementType, text, familyName) {
) : JetSelfTargetingRangeIntention<TElement>(elementType, text, familyName, firstElementOfTypeOnly) {
public abstract fun isApplicableTo(element: TElement): Boolean public abstract fun isApplicableTo(element: TElement): Boolean
@@ -18,11 +18,13 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.conversion.copy.end import org.jetbrains.kotlin.idea.conversion.copy.end
import org.jetbrains.kotlin.idea.conversion.copy.start import org.jetbrains.kotlin.idea.conversion.copy.start
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
@@ -44,6 +46,9 @@ public class AddNameToArgumentIntention
return true return true
} }
override fun allowCaretInsideElement(element: PsiElement)
= element !is JetValueArgumentList && element !is JetContainerNode
override fun applyTo(element: JetValueArgument, editor: Editor) { override fun applyTo(element: JetValueArgument, editor: Editor) {
val name = detectNameToAdd(element)!! val name = detectNameToAdd(element)!!
val newArgument = JetPsiFactory(element).createArgument(element.getArgumentExpression()!!, name, element.getSpreadElement() != null) val newArgument = JetPsiFactory(element).createArgument(element.getArgumentExpression()!!, name, element.getSpreadElement() != null)
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
@@ -25,7 +26,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.JetType
public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarationWithBody>( public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarationWithBody>(
javaClass(), "Convert to block body", firstElementOfTypeOnly = true javaClass(), "Convert to block body"
) { ) {
override fun isApplicableTo(element: JetDeclarationWithBody, caretOffset: Int): Boolean { override fun isApplicableTo(element: JetDeclarationWithBody, caretOffset: Int): Boolean {
if (element is JetFunctionLiteral || element.hasBlockBody() || !element.hasBody()) return false if (element is JetFunctionLiteral || element.hasBlockBody() || !element.hasBody()) return false
@@ -43,6 +44,8 @@ public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarat
} }
} }
override fun allowCaretInsideElement(element: PsiElement) = element !is JetDeclaration
override fun applyTo(element: JetDeclarationWithBody, editor: Editor) { override fun applyTo(element: JetDeclarationWithBody, editor: Editor) {
convert(element) convert(element)
} }
@@ -33,13 +33,15 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependentIntention<JetDeclarationWithBody>( public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependentIntention<JetDeclarationWithBody>(
javaClass(), "Convert to expression body", firstElementOfTypeOnly = true javaClass(), "Convert to expression body"
) { ) {
override fun isApplicableTo(element: JetDeclarationWithBody): Boolean { override fun isApplicableTo(element: JetDeclarationWithBody): Boolean {
val value = calcValue(element) val value = calcValue(element)
return value != null && !containsReturn(value) return value != null && !containsReturn(value)
} }
override fun allowCaretInsideElement(element: PsiElement) = element !is JetDeclaration
override fun applyTo(element: JetDeclarationWithBody, editor: Editor) { override fun applyTo(element: JetDeclarationWithBody, editor: Editor) {
applyToInternal(element) { applyToInternal(element) {
val typeRef = it.getTypeReference()!! val typeRef = it.getTypeReference()!!
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.core.moveInsideParentheses import org.jetbrains.kotlin.idea.core.moveInsideParentheses
import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument
import org.jetbrains.kotlin.psi.psiUtil.containsInside
public class MoveLambdaInsideParenthesesIntention : JetSelfTargetingIntention<JetFunctionLiteralArgument>(javaClass(), "Move lambda argument into parentheses"), LowPriorityAction { public class MoveLambdaInsideParenthesesIntention : JetSelfTargetingIntention<JetFunctionLiteralArgument>(javaClass(), "Move lambda argument into parentheses"), LowPriorityAction {
override fun isApplicableTo(element: JetFunctionLiteralArgument, caretOffset: Int): Boolean { override fun isApplicableTo(element: JetFunctionLiteralArgument, caretOffset: Int): Boolean {
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containsInside
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.JetParenthesizedExpression import org.jetbrains.kotlin.psi.JetParenthesizedExpression
import org.jetbrains.kotlin.psi.JetPsiUtil import org.jetbrains.kotlin.psi.JetPsiUtil
import org.jetbrains.kotlin.psi.psiUtil.containsInside
public class RemoveUnnecessaryParenthesesIntention : JetSelfTargetingIntention<JetParenthesizedExpression>(javaClass(), "Remove unnecessary parentheses") { public class RemoveUnnecessaryParenthesesIntention : JetSelfTargetingIntention<JetParenthesizedExpression>(javaClass(), "Remove unnecessary parentheses") {
override fun isApplicableTo(element: JetParenthesizedExpression, caretOffset: Int): Boolean { override fun isApplicableTo(element: JetParenthesizedExpression, caretOffset: Int): Boolean {
@@ -63,8 +63,6 @@ fun JetContainerNode.description(): String? {
return null return null
} }
fun TextRange.containsInside(offset: Int) = getStartOffset() < offset && offset < getEndOffset()
fun isAutoCreatedItUsage(expression: JetSimpleNameExpression): Boolean { fun isAutoCreatedItUsage(expression: JetSimpleNameExpression): Boolean {
if (expression.getReferencedName() != "it") return false if (expression.getReferencedName() != "it") return false
val context = expression.analyze() val context = expression.analyze()
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(p: Int){}
fun bar(list: List<Int>) {
foo(list[<caret>1])
}
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(p: Int){}
fun bar() {
foo("".hashCode(<caret>))
}
@@ -0,0 +1,5 @@
fun foo(p: Int){}
fun bar() {
foo("".hashCode<caret>())
}
@@ -0,0 +1,5 @@
fun foo(p: Int){}
fun bar() {
foo(p = "".hashCode<caret>())
}
@@ -130,6 +130,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("notInsideIndices.kt")
public void testNotInsideIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notInsideIndices.kt");
doTest(fileName);
}
@TestMetadata("notInsideNestedArgumentList.kt")
public void testNotInsideNestedArgumentList() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notInsideNestedArgumentList.kt");
doTest(fileName);
}
@TestMetadata("notInsideNestedArgumentList2.kt")
public void testNotInsideNestedArgumentList2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notInsideNestedArgumentList2.kt");
doTest(fileName);
}
@TestMetadata("notLast.kt") @TestMetadata("notLast.kt")
public void testNotLast() throws Exception { public void testNotLast() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notLast.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notLast.kt");