OperatorToFunctionIntention and SwapBinaryExpressionIntention: more narrow availability range + no i18n

This commit is contained in:
Valentin Kipyatkov
2015-04-14 15:10:51 +03:00
parent 00a4beae1d
commit dc6c6f13d5
25 changed files with 117 additions and 91 deletions
@@ -51,18 +51,28 @@ public class JetArrayAccessExpression extends JetExpressionImpl implements JetRe
@NotNull
public JetContainerNode getIndicesNode() {
JetContainerNode indicesNode = (JetContainerNode) findChildByType(JetNodeTypes.INDICES);
JetContainerNode indicesNode = findChildByType(JetNodeTypes.INDICES);
assert indicesNode != null : "Can't be null because of parser";
return indicesNode;
}
@NotNull
public List<TextRange> getBracketRanges() {
PsiElement lBracket = getIndicesNode().findChildByType(JetTokens.LBRACKET);
PsiElement rBracket = getIndicesNode().findChildByType(JetTokens.RBRACKET);
PsiElement lBracket = getLeftBracket();
PsiElement rBracket = getRightBracket();
if (lBracket == null || rBracket == null) {
return Collections.emptyList();
}
return Lists.newArrayList(lBracket.getTextRange(), rBracket.getTextRange());
}
@Nullable
public PsiElement getLeftBracket() {
return getIndicesNode().findChildByType(JetTokens.LBRACKET);
}
@Nullable
public PsiElement getRightBracket() {
return getIndicesNode().findChildByType(JetTokens.RBRACKET);
}
}
@@ -296,8 +296,6 @@ remove.unnecessary.curly.brackets.from.string.template=Remove curly braces from
remove.unnecessary.curly.brackets.from.string.template.family=Remove Curly Braces From Variable
insert.curly.brackets.to.string.template=Insert curly braces around variable
insert.curly.brackets.to.string.template.family=Insert Curly Braces Around Variable
swap.binary.expression=Flip binary expression
swap.binary.expression.family=Flip Binary Expression
add.name.to.argument.family=Add Name to Argument
add.name.to.argument.single=Add name to argument\: ''{0}''
add.name.to.argument.multiple=Add name to argument...
@@ -349,8 +347,6 @@ make.type.implicit.in.lambda=Make types implicit in lambda (may break code)
make.type.implicit.in.lambda.family=Make Types Implicit In Lambda (May Break Code)
invert.if.condition=Invert If Condition
invert.if.condition.family=Invert If Condition
operator.to.function=Replace overloaded operator with function call
operator.to.function.family=Replace Overloaded Operator With Function Call
convert.to.for.each.loop.intention=Replace with a for each loop
convert.to.for.each.loop.intention.family=Replace with a For Each Loop
convert.to.for.each.function.call.intention=Replace with a forEach function call
@@ -16,21 +16,24 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.JetElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
import com.intellij.codeInsight.intention.IntentionAction
import org.jetbrains.kotlin.psi.psiUtil.parents
public abstract class JetSelfTargetingIntention<T: JetElement>(
public val elementType: Class<T>,
public abstract class JetSelfTargetingIntention<TElement : JetElement>(
public val elementType: Class<TElement>,
private var text: String,
private val familyName: String)
private val familyName: String = text)
: IntentionAction {
deprecated("Use primary constructor, no need to use i18n")
public constructor(key: String, elementType: Class<T>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
public constructor(key: String, elementType: Class<TElement>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
}
protected fun setText(text: String) {
@@ -40,13 +43,28 @@ public abstract class JetSelfTargetingIntention<T: JetElement>(
final override fun getText() = text
final override fun getFamilyName() = familyName
public abstract fun isApplicableTo(element: T, caretOffset: Int): Boolean
public abstract fun isApplicableTo(element: TElement, caretOffset: Int): Boolean
public abstract fun applyTo(element: T, editor: Editor)
public abstract fun applyTo(element: TElement, editor: Editor)
private fun getTarget(editor: Editor, file: PsiFile): T? {
private fun getTarget(editor: Editor, file: PsiFile): TElement? {
val offset = editor.getCaretModel().getOffset()
return file.findElementAt(offset)?.getParentOfTypesAndPredicate(false, elementType) { element -> isApplicableTo(element, offset) }
val leaf1 = file.findElementAt(offset)
val leaf2 = file.findElementAt(offset - 1)
val commonParent = if (leaf1 != null && leaf2 != null) PsiTreeUtil.findCommonParent(leaf1, leaf2) else null
var elementsToCheck: Sequence<PsiElement> = sequence { null }
if (leaf1 != null) {
elementsToCheck += leaf1.parents().takeWhile { it != commonParent }
}
if (leaf2 != null) {
elementsToCheck += leaf2.parents().takeWhile { it != commonParent }
}
if (commonParent != null) {
elementsToCheck += commonParent.parents()
}
return elementsToCheck.filterIsInstance(elementType).firstOrNull { isApplicableTo(it, offset) }
}
final override fun isAvailable(project: Project, editor: Editor, file: PsiFile)
@@ -62,17 +80,17 @@ public abstract class JetSelfTargetingIntention<T: JetElement>(
override fun toString(): String = getText()
}
public abstract class JetSelfTargetingOffsetIndependentIntention<T: JetElement>(
elementType: Class<T>,
public abstract class JetSelfTargetingOffsetIndependentIntention<TElement : JetElement>(
elementType: Class<TElement>,
text: String,
familyName: String)
: JetSelfTargetingIntention<T>(elementType, text, familyName) {
familyName: String = text)
: JetSelfTargetingIntention<TElement>(elementType, text, familyName) {
deprecated("Use primary constructor, no need to use i18n")
public constructor(key: String, elementType: Class<T>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
public constructor(key: String, elementType: Class<TElement>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
}
public abstract fun isApplicableTo(element: T): Boolean
public abstract fun isApplicableTo(element: TElement): Boolean
override final fun isApplicableTo(element: T, caretOffset: Int): Boolean = isApplicableTo(element)
override final fun isApplicableTo(element: TElement, caretOffset: Int): Boolean = isApplicableTo(element)
}
@@ -16,49 +16,57 @@
package org.jetbrains.kotlin.idea.intentions
import org.jetbrains.kotlin.psi.JetExpression
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.JetPrefixExpression
import org.jetbrains.kotlin.psi.JetPostfixExpression
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.psi.JetArrayAccessExpression
import org.jetbrains.kotlin.psi.JetCallExpression
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetDotQualifiedExpression
import org.jetbrains.kotlin.resolve.BindingContext
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
public class OperatorToFunctionIntention : JetSelfTargetingOffsetIndependentIntention<JetExpression>("operator.to.function", javaClass()) {
public class OperatorToFunctionIntention : JetSelfTargetingIntention<JetExpression>(javaClass(), "Replace overloaded operator with function call") {
companion object {
private fun isApplicablePrefix(element: JetPrefixExpression): Boolean {
return when (element.getOperationReference().getReferencedNameElementType()) {
private fun isApplicablePrefix(element: JetPrefixExpression, caretOffset: Int): Boolean {
val opRef = element.getOperationReference()
if (!opRef.getTextRange().containsOffset(caretOffset)) return false
return when (opRef.getReferencedNameElementType()) {
JetTokens.PLUS, JetTokens.MINUS, JetTokens.PLUSPLUS, JetTokens.MINUSMINUS, JetTokens.EXCL -> true
else -> false
}
}
private fun isApplicablePostfix(element: JetPostfixExpression): Boolean {
return when (element.getOperationReference().getReferencedNameElementType()) {
private fun isApplicablePostfix(element: JetPostfixExpression, caretOffset: Int): Boolean {
val opRef = element.getOperationReference()
if (!opRef.getTextRange().containsOffset(caretOffset)) return false
return when (opRef.getReferencedNameElementType()) {
JetTokens.PLUSPLUS, JetTokens.MINUSMINUS -> true
else -> false
}
}
private fun isApplicableBinary(element: JetBinaryExpression): Boolean {
return when (element.getOperationReference().getReferencedNameElementType()) {
private fun isApplicableBinary(element: JetBinaryExpression, caretOffset: Int): Boolean {
val opRef = element.getOperationReference()
if (!opRef.getTextRange().containsOffset(caretOffset)) return false
return when (opRef.getReferencedNameElementType()) {
JetTokens.PLUS, JetTokens.MINUS, JetTokens.MUL, JetTokens.DIV, JetTokens.PERC, JetTokens.RANGE, JetTokens.IN_KEYWORD, JetTokens.NOT_IN, JetTokens.PLUSEQ, JetTokens.MINUSEQ, JetTokens.MULTEQ, JetTokens.DIVEQ, JetTokens.PERCEQ, JetTokens.EQEQ, JetTokens.EXCLEQ, JetTokens.GT, JetTokens.LT, JetTokens.GTEQ, JetTokens.LTEQ -> true
JetTokens.EQ -> element.getLeft() is JetArrayAccessExpression
else -> false
}
}
private fun isApplicableCall(element: JetCallExpression): Boolean {
val bindingContext = element.analyze()
val resolvedCall = element.getResolvedCall(bindingContext)
private fun isApplicableArrayAccess(element: JetArrayAccessExpression, caretOffset: Int): Boolean {
val lbracket = element.getLeftBracket() ?: return false
return lbracket.getTextRange().containsOffset(caretOffset)
}
private fun isApplicableCall(element: JetCallExpression, caretOffset: Int): Boolean {
val lbrace = (element.getValueArgumentList()?.getLeftParenthesis()
?: element.getFunctionLiteralArguments().firstOrNull()?.getFunctionLiteral()?.getLeftCurlyBrace()
?: return false) as PsiElement
if (!lbrace.getTextRange().containsOffset(caretOffset)) return false
val resolvedCall = element.getResolvedCall(element.analyze())
val descriptor = resolvedCall?.getResultingDescriptor()
if (descriptor is FunctionDescriptor && descriptor.getName().asString() == "invoke") {
val parent = element.getParent()
@@ -110,7 +118,7 @@ public class OperatorToFunctionIntention : JetSelfTargetingOffsetIndependentInte
if (op == JetTokens.EQ) {
if (left is JetArrayAccessExpression) {
convertArrayAccess(left as JetArrayAccessExpression)
convertArrayAccess(left)
}
return element
}
@@ -195,13 +203,13 @@ public class OperatorToFunctionIntention : JetSelfTargetingOffsetIndependentInte
}
}
override fun isApplicableTo(element: JetExpression): Boolean {
override fun isApplicableTo(element: JetExpression, caretOffset: Int): Boolean {
return when (element) {
is JetPrefixExpression -> isApplicablePrefix(element)
is JetPostfixExpression -> isApplicablePostfix(element)
is JetBinaryExpression -> isApplicableBinary(element)
is JetArrayAccessExpression -> true
is JetCallExpression -> isApplicableCall(element)
is JetPrefixExpression -> isApplicablePrefix(element, caretOffset)
is JetPostfixExpression -> isApplicablePostfix(element, caretOffset)
is JetBinaryExpression -> isApplicableBinary(element, caretOffset)
is JetArrayAccessExpression -> isApplicableArrayAccess(element, caretOffset)
is JetCallExpression -> isApplicableCall(element, caretOffset)
else -> false
}
}
+1 -1
View File
@@ -745,7 +745,7 @@
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.SwapBinaryExpression</className>
<className>org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention</className>
<category>Kotlin</category>
</intentionAction>
@@ -16,21 +16,16 @@
package org.jetbrains.kotlin.idea.intentions
import org.jetbrains.kotlin.psi.JetStringTemplateExpression
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetStringTemplateEntry
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.psi.JetStringTemplateEntryWithExpression
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.psi.JetIfExpression
import org.jetbrains.kotlin.psi.JetBlockExpression
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContextUtils
public class ConvertToConcatenatedStringIntention : JetSelfTargetingOffsetIndependentIntention<JetStringTemplateExpression>("convert.to.concatenated.string.intention", javaClass()) {
override fun isApplicableTo(element: JetStringTemplateExpression): Boolean {
if (element.getLastChild().getNode().getElementType() != JetTokens.CLOSING_QUOTE) return false // not available for unclosed literal
return element.getEntries().any { it is JetStringTemplateEntryWithExpression }
}
@@ -39,7 +34,7 @@ public class ConvertToConcatenatedStringIntention : JetSelfTargetingOffsetIndepe
val quote = if (tripleQuoted) "\"\"\"" else "\""
val entries = element.getEntries()
val result = entries.stream()
val result = entries.sequence()
.mapIndexed { index, entry ->
entry.toSeparateString(quote, convertExplicitly = index == 0, isFinalEntry = index == entries.size() - 1)
}
@@ -24,23 +24,24 @@ import org.jetbrains.kotlin.idea.util.JetPsiPrecedences
import org.jetbrains.kotlin.lexer.JetTokens.*
import org.jetbrains.kotlin.types.expressions.OperatorConventions
public class SwapBinaryExpression : JetSelfTargetingOffsetIndependentIntention<JetBinaryExpression>(
"swap.binary.expression", javaClass()
) {
public class SwapBinaryExpressionIntention : JetSelfTargetingIntention<JetBinaryExpression>(javaClass(), "Flip binary expression") {
companion object {
val SUPPORTED_OPERATIONS = setOf(PLUS, MUL, OROR, ANDAND, EQEQ, EXCLEQ, EQEQEQ, EXCLEQEQEQ, GT, LT, GTEQ, LTEQ)
private val SUPPORTED_OPERATIONS = setOf(PLUS, MUL, OROR, ANDAND, EQEQ, EXCLEQ, EQEQEQ, EXCLEQEQEQ, GT, LT, GTEQ, LTEQ)
val SUPPORTED_OPERATION_NAMES = SUPPORTED_OPERATIONS.map { OperatorConventions.BINARY_OPERATION_NAMES[it]?.asString() }.toSet().filterNotNull() +
private val SUPPORTED_OPERATION_NAMES = SUPPORTED_OPERATIONS.map { OperatorConventions.BINARY_OPERATION_NAMES[it]?.asString() }.toSet().filterNotNull() +
setOf("xor", "or", "and", "equals", "identityEquals")
}
override fun isApplicableTo(element: JetBinaryExpression): Boolean {
override fun isApplicableTo(element: JetBinaryExpression, caretOffset: Int): Boolean {
val opRef = element.getOperationReference()
if (!opRef.getTextRange().containsOffset(caretOffset)) return false
if (leftSubject(element) == null || rightSubject(element) == null) {
return false
}
val operationToken = element.getOperationToken()
val operationTokenText = element.getOperationReference().getText()
val operationTokenText = opRef.getText()
if (operationToken in SUPPORTED_OPERATIONS
|| operationToken == IDENTIFIER && operationTokenText in SUPPORTED_OPERATION_NAMES) {
setText("Flip '$operationTokenText'")
@@ -62,11 +63,9 @@ public class SwapBinaryExpression : JetSelfTargetingOffsetIndependentIntention<J
val left = leftSubject(element)!!
val right = rightSubject(element)!!
val psiFactory = JetPsiFactory(element)
val newRight = psiFactory.createExpression(left.getText()!!)
val newLeft = psiFactory.createExpression(right.getText()!!)
left.replace(newLeft)
right.replace(newRight)
element.replace(psiFactory.createBinaryExpression(element.getLeft(), convertedOperator, element.getRight()))
left.replace(psiFactory.createExpression(right.getText()))
right.replace(psiFactory.createExpression(left.getText()))
element.replace(psiFactory.createBinaryExpression(element.getLeft()!!, convertedOperator, element.getRight()!!))
}
private fun leftSubject(element: JetBinaryExpression): JetExpression? {
@@ -4,5 +4,5 @@ class coffee() {
}
}
fun main() {
val f = coffee().<caret>invoke()
val f = coffee().invoke<caret>()
}
@@ -2,5 +2,5 @@
// IS_APPLICABLE: false
fun foo() {
val bar = { x: Int -> x + 1 }
val incremented = listOf(1, 2, 3).<caret>map(bar)
val incremented = listOf(1, 2, 3).map<caret>(bar)
}
@@ -1,4 +1,4 @@
fun foo() {
var a = 5
<caret>a--
a--<caret>
}
@@ -1 +1 @@
org.jetbrains.kotlin.idea.intentions.SwapBinaryExpression
org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention
@@ -1,3 +1,3 @@
fun main() {
val ex = 4 > <caret>5
val ex = 4 ><caret> 5
}
@@ -1,3 +1,3 @@
fun main() {
val ex = 4 < <caret>5
val ex = 4 <<caret> 5
}
@@ -1,3 +1,3 @@
fun test(a: Int, b: Int, c: Int, d: Int, e: Int): Int {
return a + b + <caret>c + d + e
return a + b +<caret> c + d + e
}
@@ -1,3 +1,3 @@
fun main() {
val rabbit = 1 == 2<caret> || 3 == 5
val rabbit = 1 == 2 <caret>|| 3 == 5
}
@@ -1,5 +1,5 @@
fun main(): Boolean {
if (5 != <caret>20) {
if (5 !=<caret> 20) {
return false
}
return true
@@ -1,3 +1,3 @@
fun neq(a: Int, b: Int) {
if (<caret>a !== b) {}
if (a <caret>!== b) {}
}
@@ -1,3 +1,3 @@
fun neq(a: Int, b: Int) {
if (<caret>b !== a) {}
if (b <caret>!== a) {}
}
@@ -1,3 +1,3 @@
fun test(a: Int, b: Int): Int {
return <caret>a + b
return a <caret>+ b
}
@@ -1,3 +1,3 @@
fun test(a: Int, b: Int): Int {
return <caret>a + b
return a <caret>+ b
}
@@ -1,3 +1,3 @@
fun test(a: Int, b: Int): Int {
return <caret>a plus b
return a <caret>plus b
}
@@ -1,3 +1,3 @@
fun test(a: Int, b: Int): Int {
return <caret>a times b
return a <caret>times b
}