Rename: Initial support of naming conventions

This commit is contained in:
Alexey Sedunov
2014-08-07 14:32:19 +04:00
parent d226a11c8e
commit 67606c55e7
53 changed files with 804 additions and 158 deletions

View File

@@ -1,4 +1,27 @@
<root>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void findCollisions(com.intellij.psi.PsiElement, java.lang.String, java.util.Map&lt;? extends com.intellij.psi.PsiElement,java.lang.String&gt;, java.util.List&lt;com.intellij.usageView.UsageInfo&gt;)'>
<annotation name='kotlin.jvm.KotlinSignature'>
<val name="value"
val="&quot;fun findCollisions(element: PsiElement?, newName: String?, allRenames: Map&lt;out PsiElement?, String&gt;, result: MutableList&lt;UsageInfo&gt;): Unit&quot;"/>
</annotation>
</item>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void findCollisions(com.intellij.psi.PsiElement, java.lang.String, java.util.Map&lt;? extends com.intellij.psi.PsiElement,java.lang.String&gt;, java.util.List&lt;com.intellij.usageView.UsageInfo&gt;) 2'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void findCollisions(com.intellij.psi.PsiElement, java.lang.String, java.util.Map&lt;? extends com.intellij.psi.PsiElement,java.lang.String&gt;, java.util.List&lt;com.intellij.usageView.UsageInfo&gt;) 3'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void findExistingNameConflicts(com.intellij.psi.PsiElement, java.lang.String, com.intellij.util.containers.MultiMap&lt;com.intellij.psi.PsiElement,java.lang.String&gt;) 2'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void findExistingNameConflicts(com.intellij.psi.PsiElement, java.lang.String, com.intellij.util.containers.MultiMap&lt;com.intellij.psi.PsiElement,java.lang.String&gt;, java.util.Map&lt;com.intellij.psi.PsiElement,java.lang.String&gt;) 2'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.refactoring.rename.RenamePsiElementProcessor void prepareRenaming(com.intellij.psi.PsiElement, java.lang.String, java.util.Map&lt;com.intellij.psi.PsiElement,java.lang.String&gt;, com.intellij.psi.search.SearchScope)'>
<annotation name='jet.runtime.typeinfo.KotlinSignature'>

View File

@@ -31,40 +31,169 @@ import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
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
class object {
private 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
private 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
private 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 bindingContext = AnalyzerFacadeWithCache.getContextForElement(element)
val resolvedCall = element.getResolvedCall(bindingContext)
val descriptor = resolvedCall?.getResultingDescriptor()
if (descriptor is FunctionDescriptor && descriptor.getName().asString() == "invoke") {
private fun isApplicableCall(element: JetCallExpression): Boolean {
val bindingContext = AnalyzerFacadeWithCache.getContextForElement(element)
val resolvedCall = element.getResolvedCall(bindingContext)
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
}
private fun convertPrefix(element: JetPrefixExpression): JetExpression {
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 element
}
val transformation = "$base.$call"
val transformed = JetPsiFactory(element).createExpression(transformation)
return element.replace(transformed) as JetExpression
}
private fun convertPostFix(element: JetPostfixExpression): JetExpression {
val op = element.getOperationReference().getReferencedNameElementType()
val base = element.getBaseExpression().getText()
val call = when (op) {
JetTokens.PLUSPLUS -> "inc()"
JetTokens.MINUSMINUS -> "dec()"
else -> return element
}
val transformation = "$base.$call"
val transformed = JetPsiFactory(element).createExpression(transformation)
return element.replace(transformed) as JetExpression
}
private fun convertBinary(element: JetBinaryExpression): JetExpression {
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 element
}
val context = AnalyzerFacadeWithCache.getContextForElement(element)
val functionCandidate = element.getResolvedCall(context)
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 element
}
val transformed = JetPsiFactory(element).createExpression(transformation)
return element.replace(transformed) as JetExpression
}
private fun convertArrayAccess(element: JetArrayAccessExpression): JetExpression {
val parent = element.getParent()
if (parent is JetDotQualifiedExpression && element.getCalleeExpression()?.getText() == "invoke") return false
return !(element.getValueArgumentList() == null && element.getFunctionLiteralArguments().isEmpty())
val array = element.getArrayExpression()!!.getText()
val indices = element.getIndicesNode()
val indicesText = indices.getText()?.trim("[","]") ?: throw AssertionError("Indices node of ArrayExpression shouldn't be null: JetArrayAccessExpression = ${element.getText()}")
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(element).createExpression(transformation)
return replaced.replace(transformed) as JetExpression
}
private fun convertCall(element: JetCallExpression): JetExpression {
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(element).createExpression(transformation)
funcLitArgs.forEach { transformed.add(it) }
return callee.getParent()!!.replace(transformed) as JetExpression
}
public fun convert(element: JetExpression): JetExpression {
return when (element) {
is JetPrefixExpression -> convertPrefix(element)
is JetPostfixExpression -> convertPostFix(element)
is JetBinaryExpression -> convertBinary(element)
is JetArrayAccessExpression -> convertArrayAccess(element)
is JetCallExpression -> convertCall(element)
else -> element
}
}
return false
}
override fun isApplicableTo(element: JetExpression): Boolean {
@@ -78,128 +207,7 @@ public class OperatorToFunctionIntention : JetSelfTargetingIntention<JetExpressi
}
}
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(element).createExpression(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(element).createExpression(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 = element.getResolvedCall(context)
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(element).createExpression(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("[","]") ?: throw AssertionError("Indices node of ArrayExpression shouldn't be null: JetArrayAccessExpression = ${element.getText()}")
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(element).createExpression(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(element).createExpression(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)
}
convert(element)
}
}

View File

@@ -37,4 +37,6 @@ refactoring.move.non.kotlin.file=Target must be a Kotlin file
package.private.0.will.no.longer.be.accessible.from.1=Package-private {0} will no longer be accessible from {1}
0.uses.package.private.1={0} uses package-private {1}
0.will.no.longer.be.accessible.after.extraction={0} will no longer be accessible after extraction
0.will.become.invisible.after.extraction={0} will become invisible after extraction
0.will.become.invisible.after.extraction={0} will become invisible after extraction
naming.convention.will.be.violated.after.rename=Naming conventions will be violated after rename

View File

@@ -33,7 +33,7 @@ import org.jetbrains.jet.plugin.JetBundle;
import java.util.Map;
public class RenameJetClassProcessor extends RenamePsiElementProcessor {
public class RenameJetClassProcessor extends RenameKotlinPsiProcessor {
@Override
public boolean canProcessElement(@NotNull PsiElement element) {
return element instanceof JetClassOrObject || element instanceof KotlinLightClass;

View File

@@ -19,7 +19,6 @@ package org.jetbrains.jet.plugin.refactoring.rename;
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.psi.search.SearchScope
import org.jetbrains.jet.asJava.LightClassUtil
import org.jetbrains.jet.lang.psi.JetNamedFunction
@@ -28,7 +27,7 @@ import org.jetbrains.jet.lang.resolve.java.jetAsJava.KotlinLightMethod
import kotlin.properties.Delegates
import org.jetbrains.jet.plugin.refactoring.runReadAction
public class RenameKotlinFunctionProcessor : RenamePsiElementProcessor() {
public class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
private val javaMethodProcessorInstance by Delegates.lazy {
// KT-4250
// RenamePsiElementProcessor.EP_NAME.findExtension(javaClass<RenameJavaMethodProcessor>())!!
@@ -65,4 +64,4 @@ public class RenameKotlinFunctionProcessor : RenamePsiElementProcessor() {
is JetNamedFunction -> runReadAction { LightClassUtil.getLightClassMethod(element) }
else -> throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.refactoring.rename
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.psi.PsiElement
import com.intellij.usageView.UsageInfo
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.resolve.java.jetAsJava.KotlinLightMethod
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
override fun canProcessElement(element: PsiElement): Boolean = element is JetNamedDeclaration
override fun findCollisions(
element: PsiElement?,
newName: String?,
allRenames: Map<out PsiElement?, String>,
result: MutableList<UsageInfo>
) {
checkConflictsAndReplaceUsageInfos(result)
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.refactoring.rename
import com.intellij.psi.PsiElement
import com.intellij.usageView.UsageInfo
import java.util.ArrayList
import com.intellij.refactoring.util.MoveRenameUsageInfo
import org.jetbrains.jet.plugin.references.JetMultiDeclarationReference
import org.jetbrains.jet.lang.psi.psiUtil.getParentByTypeAndBranch
import org.jetbrains.jet.lang.psi.JetWhenConditionInRange
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
import org.jetbrains.jet.plugin.references.JetForLoopInReference
import org.jetbrains.jet.plugin.references.JetReference
import org.jetbrains.jet.plugin.references.AbstractJetReference
fun checkConflictsAndReplaceUsageInfos(result: MutableList<UsageInfo>) {
val usagesToAdd = ArrayList<UsageInfo>()
val usagesToRemove = ArrayList<UsageInfo>()
for (usageInfo in result) {
val ref = usageInfo.getReference()
if (usageInfo !is MoveRenameUsageInfo || ref !is AbstractJetReference<*> || ref.canRename()) continue
val refElement = usageInfo.getElement()
val referencedElement = usageInfo.getReferencedElement()
if (refElement != null && referencedElement != null) {
usagesToAdd.add(UnresolvableConventionViolationUsageInfo(refElement, referencedElement))
usagesToRemove.add(usageInfo)
}
}
result.removeAll(usagesToRemove)
result.addAll(usagesToAdd)
}
class UnresolvableConventionViolationUsageInfo(
element: PsiElement,
referencedElement: PsiElement
) : UnresolvableCollisionUsageInfo(element, referencedElement) {
override fun getDescription(): String = JetRefactoringBundle.message("naming.convention.will.be.violated.after.rename")
}

View File

@@ -20,14 +20,18 @@ import com.google.common.collect.Lists;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.MultiRangeReference;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import jet.runtime.typeinfo.JetValueParameter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.psi.JetArrayAccessExpression;
import org.jetbrains.jet.lang.psi.JetContainerNode;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.intentions.OperatorToFunctionIntention;
import java.util.ArrayList;
import java.util.Collection;
@@ -84,4 +88,16 @@ public class JetArrayAccessReference extends JetSimpleReference<JetArrayAccessEx
return list;
}
@Override
public boolean canRename() {
return true;
}
@SuppressWarnings("ConstantConditions")
@Nullable
@Override
public PsiElement handleElementRename(@Nullable String newElementName) {
return ReferencesPackage.renameImplicitConventionalCall(this, newElementName);
}
}

View File

@@ -19,7 +19,11 @@ package org.jetbrains.jet.plugin.references;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.MultiRangeReference;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import jet.runtime.typeinfo.JetValueParameter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
@@ -27,6 +31,7 @@ import org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.intentions.OperatorToFunctionIntention;
import java.util.ArrayList;
import java.util.Collection;
@@ -98,4 +103,16 @@ public class JetInvokeFunctionReference extends JetSimpleReference<JetCallExpres
TextRange textRange = node.getTextRange();
return textRange.shiftRight(-getExpression().getTextOffset());
}
@Override
public boolean canRename() {
return true;
}
@SuppressWarnings("ConstantConditions")
@Nullable
@Override
public PsiElement handleElementRename(@Nullable String newElementName) {
return ReferencesPackage.renameImplicitConventionalCall(this, newElementName);
}
}

View File

@@ -24,6 +24,11 @@ import java.util.Collections
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.resolve.source.PsiSourceElement
import org.jetbrains.jet.lang.resolve.source.getPsi
import com.intellij.psi.PsiElement
import com.intellij.util.IncorrectOperationException
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor
class JetMultiDeclarationReference(element: JetMultiDeclaration) : JetMultiReference<JetMultiDeclaration>(element) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
@@ -38,4 +43,13 @@ class JetMultiDeclarationReference(element: JetMultiDeclaration) : JetMultiRefer
if (start == null || end == null) return TextRange.EMPTY_RANGE
return TextRange(start.getStartOffsetInParent(), end.getStartOffsetInParent())
}
override fun canRename(): Boolean {
return resolveToDescriptors().all { it is CallableMemberDescriptor && it.getKind() == CallableMemberDescriptor.Kind.SYNTHESIZED}
}
override fun handleElementRename(newElementName: String?): PsiElement? {
if (canRename()) return expression
throw IncorrectOperationException()
}
}

View File

@@ -43,7 +43,7 @@ public trait JetReference : PsiPolyVariantReference {
public fun resolveMap(): Map<DeclarationDescriptor, Collection<PsiElement>>
}
abstract class AbstractJetReference<T : JetElement>(element: T)
public abstract class AbstractJetReference<T : JetElement>(element: T)
: PsiPolyVariantReferenceBase<T>(element), JetReference {
val expression: T
@@ -67,6 +67,7 @@ abstract class AbstractJetReference<T : JetElement>(element: T)
override fun getCanonicalText(): String = "<TBD>"
open fun canRename(): Boolean = false
override fun handleElementRename(newElementName: String?): PsiElement? = throw IncorrectOperationException()
override fun bindToElement(element: PsiElement): PsiElement = throw IncorrectOperationException()

View File

@@ -28,6 +28,15 @@ import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.jet.lang.psi.psiUtil.getOutermostNonInterleavingQualifiedElement
import org.jetbrains.jet.plugin.codeInsight.addToShorteningWaitSet
import org.jetbrains.jet.plugin.refactoring.getKotlinFqName
import org.jetbrains.jet.lang.types.expressions.OperatorConventions
import org.jetbrains.jet.lexer.JetToken
import org.jetbrains.jet.plugin.intentions.OperatorToFunctionIntention
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.resolve.DescriptorResolver
import com.intellij.util.IncorrectOperationException
import org.jetbrains.jet.lang.psi.psiUtil.getParentByTypeAndBranch
public class JetSimpleNameReference(
jetSimpleNameExpression: JetSimpleNameExpression
@@ -35,8 +44,23 @@ public class JetSimpleNameReference(
override fun getRangeInElement(): TextRange = TextRange(0, getElement().getTextLength())
override fun canRename(): Boolean {
if (expression.getParentByTypeAndBranch(javaClass<JetWhenConditionInRange>()) { getOperationReference() } != null) return false
val elementType = expression.getReferencedNameElementType()
if (elementType == JetTokens.PLUSPLUS || elementType == JetTokens.MINUSMINUS) return false
return true
}
public override fun handleElementRename(newElementName: String?): PsiElement? {
if (newElementName == null) return null;
if (!canRename()) throw IncorrectOperationException()
if (newElementName == null) return expression;
// Do not rename if the reference corresponds to synthesized component function
if ((expression.getText() ?: "").startsWith(DescriptorResolver.COMPONENT_FUNCTION_NAME_PREFIX) && resolve() is JetParameter) {
return expression
}
val psiFactory = JetPsiFactory(expression)
val element = when (expression.getReferencedNameElementType()) {
@@ -45,7 +69,30 @@ public class JetSimpleNameReference(
else -> psiFactory.createNameIdentifier(newElementName)
}
return expression.getReferencedNameElement().replace(element)
var nameElement = expression.getReferencedNameElement()
val elementType = nameElement.getNode()?.getElementType()
val opExpression =
PsiTreeUtil.getParentOfType<JetExpression>(expression, javaClass<JetUnaryExpression>(), javaClass<JetBinaryExpression>())
if (elementType is JetToken && OperatorConventions.getNameForOperationSymbol(elementType) != null && opExpression != null) {
val oldDescriptor = AnalyzerFacadeWithCache.getContextForElement(expression)[BindingContext.REFERENCE_TARGET, expression]
val newExpression = OperatorToFunctionIntention.convert(opExpression)
newExpression.accept(
object: JetTreeVisitorVoid() {
override fun visitCallExpression(expression: JetCallExpression) {
val callee = expression.getCalleeExpression() as? JetSimpleNameExpression
if (callee != null && AnalyzerFacadeWithCache.getContextForElement(callee)[BindingContext.REFERENCE_TARGET, callee] == oldDescriptor) {
nameElement = callee.getReferencedNameElement()
}
else {
super.visitCallExpression(expression)
}
}
}
)
}
return nameElement.replace(element)
}
public enum class ShorteningMode {

View File

@@ -29,6 +29,12 @@ import org.jetbrains.jet.lang.psi.JetProperty
import java.util.HashSet
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.plugin.intentions.OperatorToFunctionIntention
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.JetCallExpression
import com.intellij.psi.util.PsiTreeUtil
// Navigation element of the resolved reference
// For property accessor return enclosing property
@@ -59,3 +65,11 @@ fun PsiReference.matchesTarget(target: PsiElement): Boolean {
false
}
}
fun AbstractJetReference<out JetExpression>.renameImplicitConventionalCall(newName: String?): JetExpression {
if (newName == null) return expression
val expr = OperatorToFunctionIntention.convert(expression) as JetQualifiedExpression
val newCallee = (expr.getSelectorExpression() as JetCallExpression).getCalleeExpression()!!.getReference()!!.handleElementRename(newName)
return PsiTreeUtil.getParentOfType<JetQualifiedExpression>(newCallee, javaClass<JetQualifiedExpression>()) as JetExpression
}

View File

@@ -0,0 +1,11 @@
class A(val n: Int) {
fun foo(other: A): Int = n.compareTo(other.n)
}
fun test() {
A(0) foo A(1)
A(0).foo(A(1)) < 0
A(0).foo(A(1)) <= 0
A(0).foo(A(1)) > 0
A(0).foo(A(1)) >= 0
}

View File

@@ -0,0 +1,11 @@
class A(val n: Int) {
fun compareTo(other: A): Int = n.compareTo(other.n)
}
fun test() {
A(0) compareTo A(1)
A(0) < A(1)
A(0) <= A(1)
A(0) > A(1)
A(0) >= A(1)
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "compareTo",
"newName": "foo",
"mainFile": "compareTo.kt"
}

View File

@@ -0,0 +1,9 @@
class A(val n: Int) {
fun foo(k: Int): Boolean = k <= n
}
fun test() {
A(2) foo 1
A(2).foo(1)
!A(2).foo(1)
}

View File

@@ -0,0 +1,9 @@
class A(val n: Int) {
fun contains(k: Int): Boolean = k <= n
}
fun test() {
A(2) contains 1
1 in A(2)
1 !in A(2)
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "contains",
"newName": "foo",
"mainFile": "contains.kt"
}

View File

@@ -0,0 +1,13 @@
class A(val n: Int) {
fun contains(k: Int): Boolean = k <= n
}
fun test() {
A(2) contains 1
1 in A(2)
1 !in A(2)
when (1) {
in A(2) -> {}
!in A(2) -> {}
}
}

View File

@@ -0,0 +1,8 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "contains",
"newName": "foo",
"mainFile": "containsWithConflicts.kt",
"hint": "Naming conventions will be violated after rename\nNaming conventions will be violated after rename"
}

View File

@@ -0,0 +1,11 @@
class A(val n: Int) {
override fun foo(other: Any?): Boolean = other is A && other.n == n
}
fun test() {
A(0).foo(A(1))
!A(0).foo(A(1))
A(0) foo A(1)
A(0) === A(1)
A(0) !== A(1)
}

View File

@@ -0,0 +1,11 @@
class A(val n: Int) {
override fun equals(other: Any?): Boolean = other is A && other.n == n
}
fun test() {
A(0) == A(1)
A(0) != A(1)
A(0) equals A(1)
A(0) === A(1)
A(0) !== A(1)
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "equals",
"newName": "foo",
"mainFile": "equals.kt"
}

View File

@@ -0,0 +1,12 @@
class A(val n: Int, val s: String, val o: Any) {
fun component1(): Int = n
fun component2(): String = s
fun component3(): Any = o
}
fun test() {
val a = A(1, "2", Any())
a.n
a.component1()
val (x, y, z) = a
}

View File

@@ -0,0 +1,8 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "component1",
"newName": "foo",
"mainFile": "explicitComponentFunction.kt",
"hint": "Naming conventions will be violated after rename"
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun foo(i: Int): A = A(i)
}
fun test() {
A(1).foo(2)
A(1).foo(2)
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun get(i: Int): A = A(i)
}
fun test() {
A(1).get(2)
A(1)[2]
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "get",
"newName": "foo",
"mainFile": "get.kt"
}

View File

@@ -0,0 +1,10 @@
class A(val n: Int) {
fun inc(): A = A(n + 1)
}
fun test() {
var a = A(1)
a.inc()
++a
a++
}

View File

@@ -0,0 +1,8 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "inc",
"newName": "foo",
"mainFile": "inc.kt",
"hint": "Naming conventions will be violated after rename\nNaming conventions will be violated after rename"
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun foo(i: Int): A = A(i)
}
fun test() {
A(1).foo(2)
A(1).foo(2)
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun invoke(i: Int): A = A(i)
}
fun test() {
A(1).invoke(2)
A(1)(2)
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "invoke",
"newName": "foo",
"mainFile": "invoke.kt"
}

View File

@@ -0,0 +1,8 @@
class A {
public fun iterator(): Iterator<String> = throw IllegalStateException("")
}
fun test() {
for (a in A<String>()) {}
a.iterator()
}

View File

@@ -0,0 +1,8 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "iterator",
"newName": "foo",
"mainFile": "iterator.kt",
"hint": "Naming conventions will be violated after rename"
}

View File

@@ -0,0 +1,12 @@
class A(val n: Int) {
fun foo(m: Int): A = A(n + m)
}
fun test() {
A(1).foo(2)
A(1) foo 2
A(1).foo(2)
var a = A(0)
a = a.foo(1)
}

View File

@@ -0,0 +1,12 @@
class A(val n: Int) {
fun plus(m: Int): A = A(n + m)
}
fun test() {
A(1) + 2
A(1) plus 2
A(1).plus(2)
var a = A(0)
a += 1
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "plus",
"newName": "foo",
"mainFile": "plus.kt"
}

View File

@@ -0,0 +1,11 @@
class A(var n: Int) {
fun foo(m: Int) {
n += m
}
}
fun test() {
val a = A(0)
a.foo(1)
a.foo(1)
}

View File

@@ -0,0 +1,11 @@
class A(var n: Int) {
fun plusAssign(m: Int) {
n += m
}
}
fun test() {
val a = A(0)
a.plusAssign(1)
a += 1
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "plusAssign",
"newName": "foo",
"mainFile": "plusAssign.kt"
}

View File

@@ -0,0 +1,10 @@
class A(val n: Int) {
fun foo(i: Int, a: A) {}
}
fun test() {
var a = A(1)
a.foo(2, A(2))
a.foo(2, A(2))
a[2]
}

View File

@@ -0,0 +1,10 @@
class A(val n: Int) {
fun set(i: Int, a: A) {}
}
fun test() {
var a = A(1)
a.set(2, A(2))
a[2] = A(2)
a[2]
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "set",
"newName": "foo",
"mainFile": "set.kt"
}

View File

@@ -0,0 +1,8 @@
data class A(val foo: Int, val s: String, val o: Any)
fun test() {
val a = A(1, "2", Any())
a.foo
a.component1()
val (x, y, z) = a
}

View File

@@ -0,0 +1,8 @@
data class A(val n: Int, val s: String, val o: Any)
fun test() {
val a = A(1, "2", Any())
a.n
a.component1()
val (x, y, z) = a
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_PROPERTY",
"classFQN": "A",
"oldName": "n",
"newName": "foo",
"mainFile": "synthesizedComponentFunction.kt"
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun foo(): A = this
}
fun test() {
A(1).foo()
A(1).foo()
}

View File

@@ -0,0 +1,8 @@
class A(val n: Int) {
fun minus(): A = this
}
fun test() {
A(1).minus()
-A(1)
}

View File

@@ -0,0 +1,7 @@
{
"type": "KOTLIN_FUNCTION",
"classFQN": "A",
"oldName": "minus",
"newName": "foo",
"mainFile": "unaryMinus.kt"
}

View File

@@ -48,6 +48,8 @@ import org.jetbrains.jet.getNullableString
import org.jetbrains.jet.plugin.search.allScope
import org.jetbrains.jet.plugin.caches.resolve.getBindingContext
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
private enum class RenameType {
JAVA_CLASS
@@ -90,8 +92,10 @@ public abstract class AbstractRenameTest : MultiFileTestCase() {
Assert.fail("""Hint "$hintDirective" was expected""")
}
}
catch (hintException : CommonRefactoringUtil.RefactoringErrorHintException) {
val hintExceptionUnquoted = StringUtil.unquoteString(hintException.getMessage()!!)
catch (e : Exception) {
if (e !is RefactoringErrorHintException && e !is ConflictsInTestsException) throw e
val hintExceptionUnquoted = StringUtil.unquoteString(e.getMessage()!!)
if (hintDirective != null) {
Assert.assertEquals(hintDirective, hintExceptionUnquoted)
}

View File

@@ -36,6 +36,51 @@ public class RenameTestGenerated extends AbstractRenameTest {
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/rename"), Pattern.compile("^(.+)\\.test$"));
}
@TestMetadata("renameCompareTo/compareTo.test")
public void testRenameCompareTo_CompareTo() throws Exception {
doTest("idea/testData/refactoring/rename/renameCompareTo/compareTo.test");
}
@TestMetadata("renameContains/contains.test")
public void testRenameContains_Contains() throws Exception {
doTest("idea/testData/refactoring/rename/renameContains/contains.test");
}
@TestMetadata("renameContainsWithConflicts/containsWithConflicts.test")
public void testRenameContainsWithConflicts_ContainsWithConflicts() throws Exception {
doTest("idea/testData/refactoring/rename/renameContainsWithConflicts/containsWithConflicts.test");
}
@TestMetadata("renameEquals/equals.test")
public void testRenameEquals_Equals() throws Exception {
doTest("idea/testData/refactoring/rename/renameEquals/equals.test");
}
@TestMetadata("renameExplicitComponentFunction/explicitComponentFunction.test")
public void testRenameExplicitComponentFunction_ExplicitComponentFunction() throws Exception {
doTest("idea/testData/refactoring/rename/renameExplicitComponentFunction/explicitComponentFunction.test");
}
@TestMetadata("renameGet/get.test")
public void testRenameGet_Get() throws Exception {
doTest("idea/testData/refactoring/rename/renameGet/get.test");
}
@TestMetadata("renameInc/inc.test")
public void testRenameInc_Inc() throws Exception {
doTest("idea/testData/refactoring/rename/renameInc/inc.test");
}
@TestMetadata("renameInvoke/invoke.test")
public void testRenameInvoke_Invoke() throws Exception {
doTest("idea/testData/refactoring/rename/renameInvoke/invoke.test");
}
@TestMetadata("renameIterator/iterator.test")
public void testRenameIterator_Iterator() throws Exception {
doTest("idea/testData/refactoring/rename/renameIterator/iterator.test");
}
@TestMetadata("renameJavaClass/renameJavaClass.test")
public void testRenameJavaClass_RenameJavaClass() throws Exception {
doTest("idea/testData/refactoring/rename/renameJavaClass/renameJavaClass.test");
@@ -161,4 +206,29 @@ public class RenameTestGenerated extends AbstractRenameTest {
doTest("idea/testData/refactoring/rename/renameKotlinVarProperty/renameOverriden.test");
}
@TestMetadata("renamePlus/plus.test")
public void testRenamePlus_Plus() throws Exception {
doTest("idea/testData/refactoring/rename/renamePlus/plus.test");
}
@TestMetadata("renamePlusAssign/plusAssign.test")
public void testRenamePlusAssign_PlusAssign() throws Exception {
doTest("idea/testData/refactoring/rename/renamePlusAssign/plusAssign.test");
}
@TestMetadata("renameSet/set.test")
public void testRenameSet_Set() throws Exception {
doTest("idea/testData/refactoring/rename/renameSet/set.test");
}
@TestMetadata("renameSynthesizedComponentFunction/synthesizedComponentFunction.test")
public void testRenameSynthesizedComponentFunction_SynthesizedComponentFunction() throws Exception {
doTest("idea/testData/refactoring/rename/renameSynthesizedComponentFunction/synthesizedComponentFunction.test");
}
@TestMetadata("renameUnaryMinus/unaryMinus.test")
public void testRenameUnaryMinus_UnaryMinus() throws Exception {
doTest("idea/testData/refactoring/rename/renameUnaryMinus/unaryMinus.test");
}
}