Fixed rename for old-style properties and properties with lambdas (PY-8315)

This commit is contained in:
Andrey Vlasovskikh
2013-01-28 15:40:00 +04:00
parent 20aacf1c24
commit a9a2672557
11 changed files with 124 additions and 30 deletions
@@ -136,11 +136,11 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
/**
* Non-recursively searches for a property for which the given function is a getter, setter or deleter.
*
* @param function the function which may be an accessor
* @param callable the function which may be an accessor
* @return the property, or null
*/
@Nullable
Property findPropertyByFunction(PyFunction function);
Property findPropertyByCallable(Callable callable);
/**
* @param parent
@@ -662,14 +662,14 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
}
@Override
public Property findPropertyByFunction(PyFunction function) {
public Property findPropertyByCallable(Callable callable) {
if (myPropertyCache == null) {
myPropertyCache = initializePropertyCache();
}
for (Property property : myPropertyCache.values()) {
if (property.getGetter().valueOrNull() == function ||
property.getSetter().valueOrNull() == function ||
property.getDeleter().valueOrNull() == function) {
if (property.getGetter().valueOrNull() == callable ||
property.getSetter().valueOrNull() == callable ||
property.getDeleter().valueOrNull() == callable) {
return property;
}
}
@@ -493,7 +493,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
public Property getProperty() {
final PyClass containingClass = getContainingClass();
if (containingClass != null) {
return containingClass.findPropertyByFunction(this);
return containingClass.findPropertyByCallable(this);
}
return null;
}
@@ -8,10 +8,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.stubs.StubUpdatingIndex;
@@ -376,17 +373,6 @@ public class PyQualifiedReference extends PyReferenceImpl {
if (isLocalScope(element)) {
return false;
}
PsiElement resolveResult = resolve();
if (resolveResult instanceof PyImportedModule) {
resolveResult = resolveResult.getNavigationElement();
}
if (element instanceof PsiDirectory && resolveResult instanceof PyFile &&
PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) {
return true;
}
if (resolveResult == element) {
return true;
}
final String referencedName = myElement.getReferencedName();
if (element instanceof PyFunction && Comparing.equal(referencedName, ((PyFunction)element).getName()) &&
((PyFunction)element).getContainingClass() != null && !PyNames.INIT.equals(referencedName)) {
@@ -399,6 +385,30 @@ public class PyQualifiedReference extends PyReferenceImpl {
}
}
}
for (ResolveResult result : multiResolve(false)) {
if (result instanceof ImplicitResolveResult) {
continue;
}
PsiElement resolveResult = result.getElement();
if (isResolvedToResult(element, resolveResult)) {
return true;
}
}
return false;
}
private boolean isResolvedToResult(PsiElement element, PsiElement resolveResult) {
if (resolveResult instanceof PyImportedModule) {
resolveResult = resolveResult.getNavigationElement();
}
if (element instanceof PsiDirectory && resolveResult instanceof PyFile &&
PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) {
return true;
}
if (resolveResult == element) {
return true;
}
if (resolveResult instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)resolveResult) &&
element instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)element) && Comparing.equal(
((PyTargetExpression)resolveResult).getReferencedName(),
@@ -415,7 +425,6 @@ public class PyQualifiedReference extends PyReferenceImpl {
if (resolvesToWrapper(element, resolveResult)) {
return true;
}
return false;
}
@@ -6,10 +6,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.Processor;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.psi.Callable;
import com.jetbrains.python.psi.Property;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.toolbox.Maybe;
@@ -78,6 +75,14 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor {
}
return null;
}
final Property property = containingClass.findPropertyByCallable(function);
final PyTargetExpression site;
if (property != null) {
site = property.getDefinitionSite();
if (site != null) {
return site;
}
}
return function;
}
@@ -93,7 +98,7 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor {
});
final PyClass containingClass = function.getContainingClass();
if (containingClass != null) {
final Property property = containingClass.findPropertyByFunction(function);
final Property property = containingClass.findPropertyByCallable(function);
if (property != null) {
addRename(allRenames, newName, property.getGetter());
addRename(allRenames, newName, property.getSetter());
@@ -1,10 +1,13 @@
package com.jetbrains.python.refactoring.rename;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
@@ -35,4 +38,25 @@ public class RenamePyVariableProcessor extends RenamePyElementProcessor {
public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) {
PyCodeInsightSettings.getInstance().RENAME_SEARCH_NON_CODE_FOR_VARIABLE = enabled;
}
@Nullable
@Override
public PsiElement substituteElementToRename(PsiElement element, @Nullable Editor editor) {
if (element instanceof PyLambdaExpression) {
final PyLambdaExpression lambdaExpression = (PyLambdaExpression)element;
final ScopeOwner owner = ScopeUtil.getScopeOwner(lambdaExpression);
if (owner instanceof PyClass) {
final PyClass cls = (PyClass)owner;
final Property property = cls.findPropertyByCallable(lambdaExpression);
if (property != null) {
final PyTargetExpression site = property.getDefinitionSite();
if (site != null) {
return site;
}
}
}
return null;
}
return element;
}
}
@@ -0,0 +1,18 @@
class C(object):
def __init__(self):
self._foo = 'foo'
def get_foo(self):
return self._foo
def set_foo(self, value):
self._foo = value
def __str__(self):
return self.foo
foo = property(get_foo, set_foo)
c = C()
print(c.f<caret>oo)
@@ -0,0 +1,18 @@
class C(object):
def __init__(self):
self._foo = 'foo'
def get_foo(self):
return self._foo
def set_foo(self, value):
self._foo = value
def __str__(self):
return self.bar
bar = property(get_foo, set_foo)
c = C()
print(c.bar)
@@ -0,0 +1,5 @@
class C(object):
foo = property(lambda self: 'bar')
c = C()
print(c.f<caret>oo, c.foo)
@@ -0,0 +1,5 @@
class C(object):
bar = property(lambda self: 'bar')
c = C()
print(c.bar, c.bar)
@@ -137,6 +137,16 @@ public class PyRenameTest extends PyTestCase {
doRenameConflictTest("foo", "A variable named 'foo' is already defined in variableNameConflict.py");
}
// PY-8315
public void testRenamePropertyWithLambda() {
doTest("bar");
}
// PY-8315
public void testRenameOldStyleProperty() {
doTest("bar");
}
private void doRenameConflictTest(String newName, String expectedConflict) {
myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py");
try {