Implemented Operator To Function intention action.

Converts +a, --a, a * b, a..b, a %= b, a in b, etc. to their function call counterparts.
This commit is contained in:
Jeremy Kaplan
2014-05-12 18:03:25 +04:00
committed by Alexey Sedunov
parent 5550924dc5
commit b3842285e8
45 changed files with 432 additions and 1 deletions
@@ -413,6 +413,7 @@ fun main(args: Array<String>) {
model("intentions/makeTypeExplicitInLambda", testMethod = "doTestMakeTypeExplicitInLambda")
model("intentions/makeTypeImplicitInLambda", testMethod = "doTestMakeTypeImplicitInLambda")
model("intentions/invertIfCondition", testMethod = "doTestInvertIfCondition")
model("intentions/operatorToFunction", testMethod = "doTestOperatorToFunction")
model("intentions/convertToForEachLoop", testMethod = "doTestConvertToForEachLoop")
model("intentions/convertToForEachFunctionCall", testMethod = "doTestConvertToForEachFunctionCall")
}
@@ -0,0 +1,11 @@
a.not()
b.dec()
a.times(b)
a.get(i_1, ..., i_n)
a.set(i_1, ..., i_n, b)
a.invoke(i_1, ..., i_n)
@@ -0,0 +1,11 @@
!a
--b
a * b
a[i_1, ..., i_n]
a[i_1, ..., i_n] = b
a(i_1, ..., i_n)
@@ -0,0 +1,5 @@
<html>
<body>
Replace overloaded operator with function call
</body>
</html>
+7 -1
View File
@@ -706,7 +706,12 @@
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.jet.plugin.inspections.ExplicitGetInspection"
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.OperatorToFunctionIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.jet.plugin.inspections.ExplicitGetInspection"
displayName="Explicit 'get'"
groupName="Kotlin"
enabledByDefault="true"
@@ -748,6 +753,7 @@
<project.converterProvider implementation="org.jetbrains.jet.plugin.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
</extensions>
<extensionPoints>
@@ -314,6 +314,8 @@ 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
@@ -0,0 +1,208 @@
/*
* Copyright 2010-2014 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.
*/
package org.jetbrains.jet.plugin.intentions
import org.jetbrains.jet.lang.psi.JetExpression
import com.intellij.openapi.editor.Editor
import org.jetbrains.jet.lang.psi.JetPrefixExpression
import org.jetbrains.jet.lang.psi.JetPostfixExpression
import org.jetbrains.jet.lang.psi.JetBinaryExpression
import org.jetbrains.jet.lang.psi.JetArrayAccessExpression
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.JetDotQualifiedExpression
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression
public class OperatorToFunctionIntention : JetSelfTargetingIntention<JetExpression>("operator.to.function", javaClass()) {
fun isApplicablePrefix(element: JetPrefixExpression): Boolean {
return when (element.getOperationReference().getReferencedNameElementType()) {
JetTokens.PLUS, JetTokens.MINUS, JetTokens.PLUSPLUS, JetTokens.MINUSMINUS, JetTokens.EXCL -> true
else -> false
}
}
fun isApplicablePostfix(element: JetPostfixExpression): Boolean {
return when (element.getOperationReference().getReferencedNameElementType()) {
JetTokens.PLUSPLUS, JetTokens.MINUSMINUS -> true
else -> false
}
}
fun isApplicableBinary(element: JetBinaryExpression): Boolean {
return when (element.getOperationReference().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
}
}
fun isApplicableCall(element: JetCallExpression): Boolean {
val resolvedCall = AnalyzerFacadeWithCache.getContextForElement(element)[BindingContext.RESOLVED_CALL, element.getCalleeExpression()]
val descriptor = resolvedCall?.getResultingDescriptor()
if (descriptor is FunctionDescriptor && descriptor.getName().asString() == "invoke") {
val parent = element.getParent()
if (parent is JetDotQualifiedExpression && element.getCalleeExpression()?.getText() == "invoke") return false
return !(element.getValueArgumentList() == null && element.getFunctionLiteralArguments().isEmpty())
}
return false
}
override fun isApplicableTo(element: JetExpression): Boolean {
return when (element) {
is JetPrefixExpression -> isApplicablePrefix(element)
is JetPostfixExpression -> isApplicablePostfix(element)
is JetBinaryExpression -> isApplicableBinary(element)
is JetArrayAccessExpression -> true
is JetCallExpression -> isApplicableCall(element)
else -> false
}
}
fun convertPrefix(element: JetPrefixExpression) {
val op = element.getOperationReference().getReferencedNameElementType()
val base = element.getBaseExpression()!!.getText()
val call = when (op) {
JetTokens.PLUS -> "plus()"
JetTokens.MINUS -> "minus()"
JetTokens.PLUSPLUS -> "inc()"
JetTokens.MINUSMINUS -> "dec()"
JetTokens.EXCL -> "not()"
else -> return
}
val transformation = "$base.$call"
val transformed = JetPsiFactory.createExpression(element.getProject(), transformation)
element.replace(transformed)
}
fun convertPostFix(element: JetPostfixExpression) {
val op = element.getOperationReference().getReferencedNameElementType()
val base = element.getBaseExpression().getText()
val call = when (op) {
JetTokens.PLUSPLUS -> "inc()"
JetTokens.MINUSMINUS -> "dec()"
else -> return
}
val transformation = "$base.$call"
val transformed = JetPsiFactory.createExpression(element.getProject(), transformation)
element.replace(transformed)
}
fun convertBinary(element: JetBinaryExpression) {
val op = element.getOperationReference().getReferencedNameElementType()
val left = element.getLeft()!!
val right = element.getRight()!!
val leftText = left.getText()
val rightText = right.getText()
if (op == JetTokens.EQ) {
if (left is JetArrayAccessExpression) {
convertArrayAccess(left as JetArrayAccessExpression)
}
return
}
val context = AnalyzerFacadeWithCache.getContextForElement(element)
val functionCandidate = context[BindingContext.RESOLVED_CALL, element.getOperationReference()]
val functionName = functionCandidate?.getCandidateDescriptor()?.getName().toString()
val elemType = context[BindingContext.EXPRESSION_TYPE, left]
val transformation = when (op) {
JetTokens.PLUS -> "$leftText.plus($rightText)"
JetTokens.MINUS -> "$leftText.minus($rightText)"
JetTokens.MUL -> "$leftText.times($rightText)"
JetTokens.DIV -> "$leftText.div($rightText)"
JetTokens.PERC -> "$leftText.mod($rightText)"
JetTokens.RANGE -> "$leftText.rangeTo($rightText)"
JetTokens.IN_KEYWORD -> "$rightText.contains($leftText)"
JetTokens.NOT_IN -> "!$rightText.contains($leftText)"
JetTokens.PLUSEQ -> if (functionName == "plusAssign") "$leftText.plusAssign($rightText)" else "$leftText = $leftText.plus($rightText)"
JetTokens.MINUSEQ -> if (functionName == "minusAssign") "$leftText.minusAssign($rightText)" else "$leftText = $leftText.minus($rightText)"
JetTokens.MULTEQ -> if (functionName == "multAssign") "$leftText.multAssign($rightText)" else "$leftText = $leftText.mult($rightText)"
JetTokens.DIVEQ -> if (functionName == "divAssign") "$leftText.divAssign($rightText)" else "$leftText = $leftText.div($rightText)"
JetTokens.PERCEQ -> if (functionName == "modAssign") "$leftText.modAssign($rightText)" else "$leftText = $leftText.mod($rightText)"
JetTokens.EQEQ -> if (elemType?.isNullable() ?: true) "$leftText?.equals($rightText) ?: $rightText.identityEquals(null)" else "$leftText.equals($rightText)"
JetTokens.EXCLEQ -> if (elemType?.isNullable() ?: true) "!($leftText?.equals($rightText) ?: $rightText.identityEquals(null))" else "!$leftText.equals($rightText)"
JetTokens.GT -> "$leftText.compareTo($rightText) > 0"
JetTokens.LT -> "$leftText.compareTo($rightText) < 0"
JetTokens.GTEQ -> "$leftText.compareTo($rightText) >= 0"
JetTokens.LTEQ -> "$leftText.compareTo($rightText) <= 0"
else -> return
}
val transformed = JetPsiFactory.createExpression(element.getProject(), transformation)
element.replace(transformed)
}
fun convertArrayAccess(element: JetArrayAccessExpression) {
val parent = element.getParent()
val array = element.getArrayExpression()!!.getText()
val indices = element.getIndicesNode()
val indicesText = indices.getText().trim("[","]")
val transformation : String
val replaced : JetElement
if (parent is JetBinaryExpression && parent.getOperationReference().getReferencedNameElementType() == JetTokens.EQ) {
// part of an assignment
val right = parent.getRight()!!.getText()
transformation = "$array.set($indicesText, $right)"
replaced = parent
}
else {
transformation = "$array.get($indicesText)"
replaced = element
}
val transformed = JetPsiFactory.createExpression(element.getProject(), transformation)
replaced.replace(transformed)
}
fun convertCall(element: JetCallExpression) {
val callee = element.getCalleeExpression()!!
val arguments = element.getValueArgumentList()
val argumentString = arguments?.getText()?.trim("(", ")")
val funcLitArgs = element.getFunctionLiteralArguments()
val calleeText = callee.getText()
val transformation = if (argumentString == null) "$calleeText.invoke" else "$calleeText.invoke($argumentString)"
val transformed = JetPsiFactory.createExpression(element.getProject(), transformation)
funcLitArgs.forEach { transformed.add(it) }
callee.getParent()!!.replace(transformed)
}
override fun applyTo(element: JetExpression, editor: Editor) {
return when (element) {
is JetPrefixExpression -> convertPrefix(element)
is JetPostfixExpression -> convertPostFix(element)
is JetBinaryExpression -> convertBinary(element)
is JetArrayAccessExpression -> convertArrayAccess(element)
is JetCallExpression -> convertCall(element)
}
}
}
@@ -0,0 +1,7 @@
class Bar {
fun get(vararg args: Int) {}
}
fun foo(a: Bar, i: Int) {
a<caret>[i, 1]
}
@@ -0,0 +1,7 @@
class Bar {
fun get(vararg args: Int) {}
}
fun foo(a: Bar, i: Int) {
a.get(i, 1)
}
@@ -0,0 +1,3 @@
fun foo(a: Array<Int>, i: Int) {
a<caret>[i]
}
@@ -0,0 +1,3 @@
fun foo(a: Array<Int>, i: Int) {
a.get(i)
}
@@ -0,0 +1,5 @@
// ERROR: Unresolved reference: array
fun foo(b: Int) {
var a = array(1, 2, 3, 4, 5)
a<caret>[2, 3] = b
}
@@ -0,0 +1,5 @@
// ERROR: Unresolved reference: array
fun foo(b: Int) {
var a = array(1, 2, 3, 4, 5)
a.set(2, 3, b)
}
@@ -0,0 +1,3 @@
fun foo(a: String?, b: String?) {
a <caret>== b
}
@@ -0,0 +1,3 @@
fun foo(a: String?, b: String?) {
a?.equals(b) ?: b.identityEquals(null)
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
a !<caret>= b
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
!a.equals(b)
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun foo(a: Int, b: Array<Int>) {
a <caret>!in b
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun foo(a: Int, b: Array<Int>) {
!b.contains(a)
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
a <caret>+ b
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
a.plus(b)
}
@@ -0,0 +1,8 @@
class Bar {
fun plusAssign(arg: Bar) {}
}
fun foo(b: Bar) {
var a = Bar()
a <caret>+= b
}
@@ -0,0 +1,8 @@
class Bar {
fun plusAssign(arg: Bar) {}
}
fun foo(b: Bar) {
var a = Bar()
a.plusAssign(b)
}
@@ -0,0 +1,4 @@
fun foo(b: Int) {
var a = 0
a <caret>+= b
}
@@ -0,0 +1,4 @@
fun foo(b: Int) {
var a = 0
a = a.plus(b)
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
a<caret>..b
}
@@ -0,0 +1,3 @@
fun foo(a: Int, b: Int) {
a.rangeTo(b)
}
@@ -0,0 +1,9 @@
class bar() {
fun invoke(i: Any?, j: Any?) : Boolean {
return true
}
}
fun foo(i: Any?, j: Any?) {
val test = bar()<caret>(i, j)
}
@@ -0,0 +1,9 @@
class bar() {
fun invoke(i: Any?, j: Any?) : Boolean {
return true
}
}
fun foo(i: Any?, j: Any?) {
val test = bar().invoke(i, j)
}
@@ -0,0 +1,8 @@
class Mocha() {
fun invoke(x: Int, y: String, f: (Int) -> String) {
}
}
fun main() {
val mocha = Mocha()
val testing = mocha<caret>(1, "fire"){ (x: Int) -> "hello world" }
}
@@ -0,0 +1,8 @@
class Mocha() {
fun invoke(x: Int, y: String, f: (Int) -> String) {
}
}
fun main() {
val mocha = Mocha()
val testing = mocha.invoke(1, "fire"){ (x: Int) -> "hello world" }
}
@@ -0,0 +1,7 @@
class Mocha() {
fun invoke(f: (Int) -> String) {}
}
fun main() {
val mocha = Mocha()
val testing = mocha<caret>{ (x: Int) -> "hello world" }
}
@@ -0,0 +1,7 @@
class Mocha() {
fun invoke(f: (Int) -> String) {}
}
fun main() {
val mocha = Mocha()
val testing = mocha.invoke{ (x: Int) -> "hello world" }
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
fun foo(b: Any) {
var a: Any
a =<caret> b
}
@@ -0,0 +1,8 @@
// IS_APPLICABLE: false
class coffee() {
fun invoke() {
}
}
fun main() {
val f = coffee().<caret>invoke()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo() {
val bar = { (x: Int) -> x + 1 }
val incremented = listOf(1, 2, 3).<caret>map(bar)
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
class bar() {}
fun main() {
val x = bar<caret>()
}
@@ -0,0 +1,4 @@
fun foo() {
var a = 5
<caret>a--
}
@@ -0,0 +1,4 @@
fun foo() {
var a = 5
a.dec()
}
@@ -0,0 +1,3 @@
fun foo(a: Int) {
+<caret>a
}
@@ -0,0 +1,3 @@
fun foo(a: Int) {
a.plus()
}
@@ -0,0 +1,4 @@
fun foo() {
var a = 0
++<caret>a
}
@@ -0,0 +1,4 @@
fun foo() {
var a = 0
a.inc()
}
@@ -1,5 +1,8 @@
// "class com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase" "false"
// ERROR: Unresolved reference: SomeTest
// ACTION: Edit intention settings
// ACTION: Replace safe access expression with 'if' expression
// ACTION: Disable 'Replace Safe Access Expression with 'if' Expression'
package testing
@@ -270,6 +270,10 @@ public abstract class AbstractCodeTransformationTest extends LightCodeInsightTes
doTestIntention(path, new MakeTypeImplicitInLambdaIntention());
}
public void doTestOperatorToFunction(@NotNull String path) throws Exception {
doTestIntention(path, new OperatorToFunctionIntention());
}
public void doTestConvertToForEachLoop(@NotNull String path) throws Exception {
doTestIntention(path, new ConvertToForEachLoopIntention());
}