mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
rename keyword arguments in calls when a named parameter is renamed (PY-3890)
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user