fixed PY-8200 Introduce parameter: do not allow refactoring when there is local variable in the selection

This commit is contained in:
Ekaterina Tuzova
2013-01-15 16:42:43 +04:00
parent e72fa6e79e
commit 0bbefa7e77
8 changed files with 95 additions and 22 deletions
@@ -2,6 +2,7 @@ package com.jetbrains.python.refactoring.introduce.parameter;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
@@ -61,32 +62,55 @@ public class PyIntroduceParameterHandler extends IntroduceHandler {
protected boolean isValidIntroduceContext(PsiElement element) {
if (element != null) {
final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(element);
final boolean[] isValid = {true};
if (scopeOwner != null) {
new PyRecursiveElementVisitor() {
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
super.visitPyReferenceExpression(node);
if (ControlFlowCache.getScope(scopeOwner).containsDeclaration(node.getName())) {
isValid[0] = false;
}
}
}.visitElement(element);
}
final PyStatement nonlocalStatement =
PsiTreeUtil.getParentOfType(element, PyNonlocalStatement.class, PyGlobalStatement.class);
final PyStatementList statementList =
PsiTreeUtil.getParentOfType(element, PyStatementList.class);
PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class);
return function != null && !isResolvedToParameter(element) && isValid[0] && nonlocalStatement == null &&
statementList != null && importStatement == null;
if (!isValidPlace(element)) return false;
return isNotDeclared(element);
}
return false;
}
private boolean isResolvedToParameter(PsiElement element) {
private static boolean isNotDeclared(PsiElement element) {
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(element);
final boolean[] isValid = {true};
if (scopeOwner != null) {
final String name = element instanceof PsiNamedElement? ((PsiNamedElement)element).getName() : element.getText();
if (name != null && ControlFlowCache.getScope(scopeOwner).containsDeclaration(name)) {
return false;
}
new PyRecursiveElementVisitor() {
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
super.visitPyReferenceExpression(node);
final String name = node.getName();
if (name != null && ControlFlowCache.getScope(scopeOwner).containsDeclaration(name)) {
isValid[0] = false;
}
}
}.visitElement(element);
}
return !isResolvedToParameter(element) && isValid[0];
}
private static boolean isValidPlace(PsiElement element) {
final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);
final PyForPart forPart = PsiTreeUtil.getParentOfType(element, PyForPart.class);
if (forPart != null) {
final PyExpression target = forPart.getTarget();
if (target instanceof PyTargetExpression && element.getText().equals(target.getName()))
return false;
}
final PyStatement nonlocalStatement =
PsiTreeUtil.getParentOfType(element, PyNonlocalStatement.class, PyGlobalStatement.class);
final PyStatementList statementList =
PsiTreeUtil.getParentOfType(element, PyStatementList.class);
PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class);
return nonlocalStatement == null && importStatement == null &&
statementList != null && function != null;
}
private static boolean isResolvedToParameter(PsiElement element) {
while (element instanceof PyReferenceExpression) {
final PsiReference reference = element.getReference();
if (reference != null && reference.resolve() instanceof PyNamedParameter)
@@ -0,0 +1,2 @@
def f(b=<caret>"myValue"):
pass
@@ -0,0 +1,4 @@
def f2():
param = 1
def f():
global <selection>param</selection>
@@ -0,0 +1,3 @@
def f():
a = 1
print <caret>a + 3
@@ -0,0 +1,2 @@
for <caret>item in range(100):
pass
@@ -0,0 +1,2 @@
def f(b=1):
return <caret>b + 1
@@ -0,0 +1,4 @@
def f2():
param = 1
def f():
nonlocal <selection>param</selection>
@@ -3,6 +3,7 @@ package com.jetbrains.python.refactoring;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import com.jetbrains.python.refactoring.introduce.parameter.PyIntroduceParameterHandler;
@@ -35,6 +36,27 @@ public class PyIntroduceParameterTest extends PyIntroduceTestCase {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"));
}
public void testLocalVariable1() {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"));
}
public void testLocalVariableParam() {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"));
}
public void testNonLocal() {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"), LanguageLevel.PYTHON32);
}
public void testGlobal() {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"));
}
public void testFunctionDef() {
doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error"));
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/refactoring/introduceParameter";
@@ -52,4 +74,14 @@ public class PyIntroduceParameterTest extends PyIntroduceTestCase {
assertEquals(expected, e.getMessage());
}
}
private void doTestCannotPerform(String expected, LanguageLevel languageLevel) {
setLanguageLevel(languageLevel);
try {
doTestCannotPerform(expected);
}
finally {
setLanguageLevel(null);
}
}
}