Implement rebinding of simple name references

This commit is contained in:
Alexey Sedunov
2014-02-19 19:56:25 +04:00
parent 86619f5f28
commit d439de4330
7 changed files with 247 additions and 3 deletions
@@ -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
}
}
+1
View File
@@ -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"/>
@@ -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();