rename keyword arguments in calls when a named parameter is renamed (PY-3890)

This commit is contained in:
Dmitry Jemerov
2011-08-10 17:49:21 +02:00
parent e28cb816d5
commit efc2abc074
13 changed files with 183 additions and 6 deletions
@@ -253,6 +253,7 @@
order="before completionAutoPopup"/>
<referencesSearch implementation="com.jetbrains.python.psi.search.PyInitReferenceSearchExecutor"/>
<referencesSearch implementation="com.jetbrains.python.psi.search.PyKeywordArgumentSearchExecutor"/>
<referencesSearch implementation="com.jetbrains.python.psi.search.PyStringReferenceSearch"/>
<findUsagesHandlerFactory implementation="com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory" id="Python"
order="last, before default"/>
@@ -276,6 +277,8 @@
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyStringLiteralExpression"
implementationClass="com.jetbrains.python.psi.impl.PyStringLiteralExpressionManipulator"/>
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyKeywordArgument"
implementationClass="com.jetbrains.python.psi.impl.PyKeywordArgumentManipulator"/>
<analyzeStacktraceFilter implementation="com.jetbrains.python.run.PythonTracebackFilter"/>
@@ -41,7 +41,9 @@ public abstract class PyElementGenerator {
public abstract PyBinaryExpression createBinaryExpression(String s, PyExpression expr, PyExpression listLiteral);
public abstract PyExpression createExpressionFromText(String text);
public abstract PyExpression createExpressionFromText(String text);
public abstract PyExpression createExpressionFromText(final LanguageLevel languageLevel, String text);
public abstract PsiElement insertItemIntoList(PyElement list, @Nullable PyExpression afterThis, PyExpression toInsert)
throws IncorrectOperationException;
@@ -191,7 +191,11 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
}
public PyExpression createExpressionFromText(final String text) {
final PsiFile dummyFile = createDummyFile(LanguageLevel.getDefault(), text);
return createExpressionFromText(LanguageLevel.getDefault(), text);
}
public PyExpression createExpressionFromText(final LanguageLevel languageLevel, final String text) {
final PsiFile dummyFile = createDummyFile(languageLevel, text);
final PyExpressionStatement expressionStatement = (PyExpressionStatement)dummyFile.getFirstChild();
return expressionStatement.getExpression();
}
@@ -1,6 +1,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.PyExpression;
@@ -43,4 +44,13 @@ public class PyKeywordArgumentImpl extends PyElementImpl implements PyKeywordArg
final PyExpression e = getValueExpression();
return e != null ? e.getType(context) : null;
}
@Override
public PsiReference getReference() {
final ASTNode keywordNode = getKeywordNode();
if (keywordNode != null) {
return new PyKeywordArgumentReference(this, keywordNode.getTextRange().shiftRight(-getTextRange().getStartOffset()));
}
return null;
}
}
@@ -0,0 +1,29 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.AbstractElementManipulator;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyCallExpression;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyKeywordArgument;
/**
* @author yole
*/
public class PyKeywordArgumentManipulator extends AbstractElementManipulator<PyKeywordArgument> {
@Override
public PyKeywordArgument handleContentChange(PyKeywordArgument element, TextRange range, String newContent) throws IncorrectOperationException {
final ASTNode keywordNode = element.getKeywordNode();
if (keywordNode != null && keywordNode.getTextRange().shiftRight(-element.getTextRange().getStartOffset()).equals(range)) {
final LanguageLevel langLevel = LanguageLevel.forElement(element);
final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
final PyCallExpression callExpression = (PyCallExpression) generator.createExpressionFromText(langLevel, "foo(" + newContent + "=None)");
final PyKeywordArgument kwArg = callExpression.getArgumentList().getKeywordArgument(newContent);
element.getKeywordNode().getPsi().replace(kwArg.getKeywordNode().getPsi());
return element;
}
throw new IncorrectOperationException("unsupported manipulation on keyword argument");
}
}
@@ -0,0 +1,68 @@
package com.jetbrains.python.psi.impl;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author yole
*/
public class PyKeywordArgumentReference extends PsiReferenceBase.Poly<PyKeywordArgument> {
public PyKeywordArgumentReference(@NotNull PyKeywordArgument element, TextRange textRange) {
super(element, textRange, true);
}
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
PyCallExpression call = PsiTreeUtil.getParentOfType(myElement, PyCallExpression.class);
assert call != null;
final PyExpression callee = call.getCallee();
if (callee == null) return ResolveResult.EMPTY_ARRAY;
final PsiPolyVariantReference calleeReference = (PsiPolyVariantReference) callee.getReference();
if (calleeReference == null) return ResolveResult.EMPTY_ARRAY;
final ResolveResult[] calleeCandidates = calleeReference.multiResolve(incompleteCode);
List<ResolveResult> resultList = new ArrayList<ResolveResult>();
for (ResolveResult calleeCandidate : calleeCandidates) {
if (!calleeCandidate.isValidResult()) continue;
final PsiElement element = calleeCandidate.getElement();
if (element == null) continue;
final PyFunction calleeFunction = resolveToFunction(element);
if (calleeFunction != null) {
final PsiElement result = calleeFunction.getParameterList().getElementNamed(myElement.getKeyword());
if (result != null) {
resultList.add(new PsiElementResolveResult(result));
}
}
}
return resultList.toArray(new ResolveResult[resultList.size()]);
}
@Nullable
private static PyFunction resolveToFunction(PsiElement element) {
if (element instanceof PyFunction) {
return (PyFunction) element;
}
if (element instanceof PyTargetExpression) {
final PyExpression assignedValue = ((PyTargetExpression)element).findAssignedValue();
return resolveToFunction(assignedValue);
}
if (element instanceof PyReferenceExpression) {
final PsiElement resolveResult = ((PyReferenceExpression)element).getReference().resolve();
return resolveToFunction(resolveResult);
}
return null;
}
@NotNull
@Override
public Object[] getVariants() {
return new Object[0]; //To change body of implemented methods use File | Settings | File Templates.
}
}
@@ -0,0 +1,44 @@
package com.jetbrains.python.psi.search;
import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Processor;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
/**
* @author yole
*/
public class PyKeywordArgumentSearchExecutor extends QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters> {
@Override
public void processQuery(@NotNull ReferencesSearch.SearchParameters queryParameters, @NotNull final Processor<PsiReference> consumer) {
final PsiElement element = queryParameters.getElementToSearch();
if (!(element instanceof PyNamedParameter)) {
return;
}
PyFunction owner = PsiTreeUtil.getParentOfType(element, PyFunction.class);
if (owner == null) {
return;
}
ReferencesSearch.search(owner, queryParameters.getScope()).forEach(new Processor<PsiReference>() {
@Override
public boolean process(PsiReference reference) {
final PsiElement refElement = reference.getElement();
final PyCallExpression call = PsiTreeUtil.getParentOfType(refElement, PyCallExpression.class);
if (call != null && PsiTreeUtil.isAncestor(call.getCallee(), refElement, false)) {
final PyArgumentList argumentList = call.getArgumentList();
if (argumentList != null) {
final PyKeywordArgument keywordArgument = argumentList.getKeywordArgument(((PyNamedParameter)element).getName());
if (keywordArgument != null) {
return consumer.process(keywordArgument.getReference());
}
}
}
return true;
}
});
}
}
@@ -6,7 +6,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHandler;
import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHandler;
@@ -62,9 +61,6 @@ public class RefactoringProvider extends RefactoringSupportProvider {
if (element instanceof PyTargetExpression || element instanceof PyFunction || element instanceof PyClass) {
return true;
}
if (element instanceof PyNamedParameter) {
return containingFunction.getContainingClass() == null;
}
}
return false;
}
@@ -0,0 +1,4 @@
def foo(b<caret>ar=True):
pass
foo(bar=False)
@@ -0,0 +1,4 @@
def foo(baz=True):
pass
foo(baz=False)
@@ -0,0 +1,5 @@
def foo(bar=True):
pass
foo(bar=False)
# <ref>
@@ -417,4 +417,8 @@ public class PyResolveTest extends PyResolveTestCase {
public void testDontResolveTargetToBuiltins() { // PY-4256
assertResolvesTo(PyTargetExpression.class, "str");
}
public void testKeywordArgument() {
assertResolvesTo(PyNamedParameter.class, "bar");
}
}
@@ -77,6 +77,10 @@ public class PyRenameTest extends PyLightFixtureTestCase {
public void testRenameSlots() { // PY-4195
doTest("bacon");
}
public void testRenameKeywordArgument() { // PY-3890
doTest("baz");
}
public void testClassNameConflict() { // PY-2390
doRenameConflictTest("Foo", "A class named 'Foo' is already defined in classNameConflict.py");