Implement rebinding of simple name references
This commit is contained in:
@@ -3,4 +3,7 @@
|
||||
name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.refactoring.RefactoringHelper void performOperation(com.intellij.openapi.project.Project, T) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -19,6 +19,7 @@ package org.jetbrains.jet.lang.psi;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.SmartList;
|
||||
import jet.runtime.typeinfo.KotlinSignature;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.JetNodeTypes;
|
||||
@@ -85,6 +86,7 @@ public class JetCallExpression extends JetReferenceExpression implements JetCall
|
||||
return result;
|
||||
}
|
||||
|
||||
@KotlinSignature("fun getValueArguments(): MutableList<out ValueArgument>")
|
||||
@Override
|
||||
@NotNull
|
||||
public List<? extends ValueArgument> getValueArguments() {
|
||||
|
||||
@@ -39,8 +39,6 @@ import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.search.PsiSearchScopeUtil
|
||||
import org.jetbrains.jet.lang.psi.JetClassBody
|
||||
import org.jetbrains.jet.lang.psi.JetParameterList
|
||||
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import org.jetbrains.jet.lang.psi.JetObjectDeclaration
|
||||
import org.jetbrains.jet.lang.psi.JetNamedFunction
|
||||
import org.jetbrains.jet.lang.psi.JetProperty
|
||||
@@ -49,6 +47,14 @@ import org.jetbrains.jet.lang.psi.JetPropertyAccessor
|
||||
import org.jetbrains.jet.lang.psi.JetParameter
|
||||
import com.intellij.psi.PsiParameterList
|
||||
import com.intellij.psi.PsiParameter
|
||||
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
|
||||
import org.jetbrains.jet.lang.psi.JetUserType
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.lang.psi.JetCallExpression
|
||||
import com.intellij.psi.PsiPackage
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiMember
|
||||
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
|
||||
|
||||
fun PsiElement.getParentByTypesAndPredicate<T: PsiElement>(
|
||||
strict : Boolean = false, vararg parentClasses : Class<T>, predicate: (T) -> Boolean
|
||||
@@ -209,4 +215,65 @@ fun PsiElement.parameterIndex(): Int {
|
||||
this is PsiParameter && parent is PsiParameterList -> parent.getParameterIndex(this)
|
||||
else -> -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enclosing qualifying element for given [[JetSimpleNameExpression]]
|
||||
* ([[JetQualifiedExpression]] or [[JetUserType]] or original expression)
|
||||
*/
|
||||
fun JetSimpleNameExpression.getQualifiedElement(): JetElement {
|
||||
val baseExpression = (getParent() as? JetCallExpression) ?: this
|
||||
val parent = baseExpression.getParent()
|
||||
return when (parent) {
|
||||
is JetQualifiedExpression -> if (parent.getSelectorExpression().isAncestor(baseExpression)) parent else baseExpression
|
||||
is JetUserType -> if (parent.getReferenceExpression().isAncestor(baseExpression)) parent else baseExpression
|
||||
else -> baseExpression
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns rightmost selector of the qualified element (null if there is no such selector)
|
||||
*/
|
||||
fun JetElement.getQualifiedElementSelector(): JetElement? {
|
||||
return when (this) {
|
||||
is JetSimpleNameExpression -> this
|
||||
is JetQualifiedExpression -> {
|
||||
val selector = getSelectorExpression()
|
||||
if (selector is JetCallExpression) selector.getCalleeExpression() else selector
|
||||
}
|
||||
is JetUserType -> getReferenceExpression()
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns outermost qualified element ([[JetQualifiedExpression]] or [[JetUserType]]) in the non-interleaving chain
|
||||
* of qualified elements which enclose given expression
|
||||
* If there is no such elements original expression is returned
|
||||
*/
|
||||
fun JetSimpleNameExpression.getOutermostNonInterleavingQualifiedElement(): JetElement {
|
||||
var element = ((getParent() as? JetCallExpression) ?: this).getParent()
|
||||
if (element !is JetQualifiedExpression && element !is JetUserType) return this
|
||||
|
||||
while (true) {
|
||||
val parent = element!!.getParent()
|
||||
if (parent !is JetQualifiedExpression && parent !is JetUserType) return element as JetElement
|
||||
element = parent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FqName for given declaration (either Java or Kotlin)
|
||||
*/
|
||||
fun PsiElement.getFqName(): FqName? {
|
||||
return when (this) {
|
||||
is PsiPackage -> FqName(getQualifiedName())
|
||||
is PsiClass -> getQualifiedName()?.let { FqName(it) }
|
||||
is PsiMember -> getName()?.let { name ->
|
||||
val prefix = getContainingClass()?.getQualifiedName()
|
||||
FqName(if (prefix != null) "$prefix.$name" else name)
|
||||
}
|
||||
is JetNamedDeclaration -> JetPsiUtil.getFQName(this)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@
|
||||
implementationClass="org.jetbrains.jet.plugin.codeInsight.surroundWith.statement.KotlinStatementSurroundDescriptor"/>
|
||||
<lang.unwrapDescriptor language="jet" implementationClass="org.jetbrains.jet.plugin.codeInsight.unwrap.KotlinUnwrapDescriptor"/>
|
||||
<quoteHandler fileType="Kotlin" className="org.jetbrains.jet.plugin.editor.KotlinQuoteHandler"/>
|
||||
<refactoring.helper implementation="org.jetbrains.jet.plugin.codeInsight.KotlinShortenReferencesRefactoringHelper"/>
|
||||
<refactoring.moveHandler implementation="org.jetbrains.jet.plugin.refactoring.move.JetMoveFilesOrDirectoriesHandler"/>
|
||||
<refactoring.copyHandler implementation="org.jetbrains.jet.plugin.refactoring.copy.JetCopyClassHandler"/>
|
||||
<refactoring.changeSignatureUsageProcessor implementation="org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureUsageProcessor"/>
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.codeInsight
|
||||
|
||||
import com.intellij.refactoring.RefactoringHelper
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.openapi.project.Project
|
||||
import java.util.HashSet
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.plugin.refactoring.changeQualifiedName
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getOutermostNonInterleavingQualifiedElement
|
||||
|
||||
public class KotlinShortenReferencesRefactoringHelper: RefactoringHelper<Set<ReferenceBindRequest>> {
|
||||
override fun prepareOperation(usages: Array<out UsageInfo>?): Set<ReferenceBindRequest>? {
|
||||
if (usages != null && usages.isNotEmpty()) {
|
||||
ApplicationManager.getApplication()!!.runWriteAction { usages[0].getProject().getElementsToShorten(true)!!.clear() }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun performOperation(project: Project, operationData: Set<ReferenceBindRequest>?) {
|
||||
ApplicationManager.getApplication()!!.runWriteAction {
|
||||
project.getElementsToShorten(false)?.let { bindRequests ->
|
||||
project.putUserData(ELEMENTS_TO_SHORTEN_KEY, null)
|
||||
ShortenReferences.process(
|
||||
bindRequests
|
||||
.map() { req -> req.process()?.getOutermostNonInterleavingQualifiedElement() }
|
||||
.filterNotNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ELEMENTS_TO_SHORTEN_KEY = Key.create<MutableSet<ReferenceBindRequest>>("ELEMENTS_TO_SHORTEN_KEY")
|
||||
|
||||
class ReferenceBindRequest(val refExpression: SmartPsiElementPointer<JetSimpleNameExpression>, val fqName: FqName) {
|
||||
fun process(): JetSimpleNameExpression? {
|
||||
fun bindToFqName(expression: JetSimpleNameExpression, fqName: FqName): JetSimpleNameExpression {
|
||||
val qualifier = expression.changeQualifiedName(fqName)
|
||||
val newExpression = qualifier.getQualifiedElementSelector() as JetSimpleNameExpression?
|
||||
assert(newExpression != null) { "No selector in qualified element" }
|
||||
|
||||
return newExpression!!
|
||||
}
|
||||
|
||||
val originalExpression = refExpression.getElement()
|
||||
if (originalExpression == null) return null
|
||||
|
||||
return bindToFqName(originalExpression, fqName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Project.getElementsToShorten(createIfNeeded: Boolean): MutableSet<ReferenceBindRequest>? {
|
||||
var elementsToShorten = getUserData(ELEMENTS_TO_SHORTEN_KEY)
|
||||
if (createIfNeeded && elementsToShorten == null) {
|
||||
elementsToShorten = HashSet()
|
||||
putUserData(ELEMENTS_TO_SHORTEN_KEY, elementsToShorten)
|
||||
}
|
||||
|
||||
return elementsToShorten
|
||||
}
|
||||
|
||||
public fun Project.addReferenceBindRequest(request: ReferenceBindRequest) {
|
||||
assert (ApplicationManager.getApplication()!!.isWriteAccessAllowed(), "Write access needed")
|
||||
getElementsToShorten(true)!!.add(request)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName
|
||||
import org.jetbrains.jet.lang.psi.JetElement
|
||||
import org.jetbrains.jet.lang.psi.JetCallExpression
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement
|
||||
import org.jetbrains.jet.lang.psi.JetUserType
|
||||
import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN
|
||||
|
||||
/**
|
||||
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
|
||||
* Result is either the same as original element, or [[JetQualifiedExpression]], or [[JetUserType]]
|
||||
* Note that FqName may not be empty
|
||||
*/
|
||||
fun JetSimpleNameExpression.changeQualifiedName(fqName: FqName): JetElement {
|
||||
assert (!fqName.isRoot(), "Can't set empty FqName for element $this")
|
||||
|
||||
val project = getProject()
|
||||
|
||||
val shortName = fqName.shortName().asString()
|
||||
val fqNameBase = (getParent() as? JetCallExpression)?.let { parent ->
|
||||
val callCopy = parent.copy() as JetCallExpression
|
||||
callCopy.getCalleeExpression()!!.replace(JetPsiFactory.createSimpleName(project, shortName)).getParent()!!.getText()
|
||||
} ?: shortName
|
||||
|
||||
val text = if (!fqName.isOneSegmentFQN()) "${fqName.parent().asString()}.$fqNameBase" else fqNameBase
|
||||
|
||||
val elementToReplace = getQualifiedElement()
|
||||
return when (elementToReplace) {
|
||||
is JetUserType -> elementToReplace.replace(JetPsiFactory.createType(project, text).getTypeElement()!!)
|
||||
else -> elementToReplace.replace(JetPsiFactory.createExpression(project, text))
|
||||
} as JetElement
|
||||
}
|
||||
@@ -19,15 +19,19 @@ package org.jetbrains.jet.plugin.references;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory;
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.PsiUtilPackage;
|
||||
import org.jetbrains.jet.lang.resolve.name.FqName;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
import org.jetbrains.jet.plugin.codeInsight.CodeInsightPackage;
|
||||
import org.jetbrains.jet.plugin.codeInsight.ReferenceBindRequest;
|
||||
|
||||
public class JetSimpleNameReference extends JetSimpleReference<JetSimpleNameExpression> {
|
||||
|
||||
public JetSimpleNameReference(@NotNull JetSimpleNameExpression jetSimpleNameExpression) {
|
||||
super(jetSimpleNameExpression);
|
||||
}
|
||||
@@ -55,6 +59,35 @@ public class JetSimpleNameReference extends JetSimpleReference<JetSimpleNameExpr
|
||||
return getExpression().getReferencedNameElement().replace(element);
|
||||
}
|
||||
|
||||
// By default reference binding is delayed
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElement bindToElement(@NotNull PsiElement element) {
|
||||
return bindToElement(element, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiElement bindToElement(@NotNull PsiElement element, boolean bindImmediately) {
|
||||
Project project = element.getProject();
|
||||
JetSimpleNameExpression currentExpression = getExpression();
|
||||
|
||||
FqName fqName = PsiUtilPackage.getFqName(element);
|
||||
if (fqName == null) return currentExpression;
|
||||
|
||||
ReferenceBindRequest bindRequest = new ReferenceBindRequest(
|
||||
SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(getExpression()),
|
||||
fqName
|
||||
);
|
||||
|
||||
if (bindImmediately) {
|
||||
JetSimpleNameExpression newExpression = bindRequest.process();
|
||||
return (newExpression != null) ? newExpression : currentExpression;
|
||||
}
|
||||
|
||||
CodeInsightPackage.addReferenceBindRequest(project, bindRequest);
|
||||
return currentExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JetSimpleNameReference.class.getSimpleName() + ": " + getExpression().getText();
|
||||
|
||||
Reference in New Issue
Block a user