From 2b5e738a5a44f524d5293c857137c994f10c3cdd Mon Sep 17 00:00:00 2001 From: Anton Bragin Date: Wed, 11 Jul 2018 14:52:19 +0300 Subject: [PATCH] PY-30827 PyRenameElementQuickFix changed to avoid special handling of unit test cases Test data and PyQuickFixTest are updated accordingly. Test for renaming in language injection added. --- .../quickfix/PyRenameElementQuickFix.java | 31 ++--- .../RenameFunctionShadowingBuiltins_after.py | 4 +- .../RenameShadowingBuiltins_after.py | 4 +- .../PyRenameElementQuickFixTest/pep8Class.py | 2 +- .../pep8Class_after.py | 2 +- .../pep8Function_after.py | 2 +- .../PyRenameElementQuickFixTest/pep8_after.py | 2 +- .../protectedMember_after.py | 4 +- .../renameAsyncClassInPy36_after.py | 2 +- .../renameAsyncFunctionInPy36_after.py | 2 +- .../renameAsyncVariableInPy36_after.py | 2 +- .../renameAwaitClassInPy36_after.py | 2 +- .../renameAwaitFunctionInPy36_after.py | 2 +- .../renameAwaitVariableInPy36_after.py | 2 +- .../renameInInjectedFragment.py | 4 + .../renameInInjectedFragment_after.py | 4 + .../shadowingBuiltins_after.py | 2 +- .../com/jetbrains/python/PyQuickFixTest.java | 2 + .../PyRenameElementQuickFixTest.java | 111 ++++++++++++++++++ 19 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment.py create mode 100644 python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment_after.py diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyRenameElementQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyRenameElementQuickFix.java index f3584f73ff63..e63bfc60dbe8 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyRenameElementQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyRenameElementQuickFix.java @@ -2,7 +2,6 @@ package com.jetbrains.python.inspections.quickfix; import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; @@ -13,8 +12,6 @@ import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.rename.PsiElementRenameHandler; -import com.intellij.refactoring.rename.RenameProcessor; -import com.intellij.refactoring.rename.RenamePsiElementProcessor; import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; import com.jetbrains.python.PyBundle; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; @@ -57,18 +54,14 @@ public class PyRenameElementQuickFix extends LocalQuickFixAndIntentionActionOnPs final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? (PsiNameIdentifierOwner)element : PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); - if (nameOwner != null && editor != null) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - renameInUnitTestMode(project, nameOwner, editor); - } - else { - if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { - new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); - } - else { - PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); - } - } + if (nameOwner == null || editor == null) { + return; + } + if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { + new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); + } + else { + PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); } } @@ -93,12 +86,4 @@ public class PyRenameElementQuickFix extends LocalQuickFixAndIntentionActionOnPs public PsiElement getElementToMakeWritable(@NotNull PsiFile file) { return file; } - - private static void renameInUnitTestMode(@NotNull Project project, @NotNull PsiNameIdentifierOwner nameOwner, - @Nullable Editor editor) { - final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); - if (substitution != null) { - new RenameProcessor(project, substitution, "a", false, false).run(); - } - } } diff --git a/python/testData/inspections/RenameFunctionShadowingBuiltins_after.py b/python/testData/inspections/RenameFunctionShadowingBuiltins_after.py index 57ac3e896508..4c318970ef8c 100644 --- a/python/testData/inspections/RenameFunctionShadowingBuiltins_after.py +++ b/python/testData/inspections/RenameFunctionShadowingBuiltins_after.py @@ -1,6 +1,6 @@ -def a(x): +def A_NEW_NAME(x): return x def f(): - return a('foo') + return A_NEW_NAME('foo') diff --git a/python/testData/inspections/RenameShadowingBuiltins_after.py b/python/testData/inspections/RenameShadowingBuiltins_after.py index 4f8354971055..ef5c9fecdcb7 100644 --- a/python/testData/inspections/RenameShadowingBuiltins_after.py +++ b/python/testData/inspections/RenameShadowingBuiltins_after.py @@ -1,3 +1,3 @@ def f(name): - a = open(name, 'rb') - return a.read() + A_NEW_NAME = open(name, 'rb') + return A_NEW_NAME.read() diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class.py index 8a2263355d69..1a62ada16de6 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class.py @@ -1,2 +1,2 @@ -class a_b: +class lowercase: pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class_after.py index b3f1e941b6f6..fe96ffcde79a 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Class_after.py @@ -1,2 +1,2 @@ -class a: +class A_NEW_NAME: pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Function_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Function_after.py index b77eecbf7a5f..c13b264a0fac 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Function_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8Function_after.py @@ -1 +1 @@ -def a(): pass \ No newline at end of file +def A_NEW_NAME(): pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8_after.py index 35d3edfb9784..6c7d3ce4775b 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/pep8_after.py @@ -1,2 +1,2 @@ def foo(): - a = "test" \ No newline at end of file + abc = "test" \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/protectedMember_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/protectedMember_after.py index 878732079fb8..0a5f7d200f4d 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/protectedMember_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/protectedMember_after.py @@ -2,7 +2,7 @@ class A: def __init__(self): - self.a = 1 + self.A_NEW_NAME = 1 def _foo(self): pass @@ -10,5 +10,5 @@ class A: a_class = A() a_class._foo() -print(a_class.a) +print(a_class.A_NEW_NAME) diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncClassInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncClassInPy36_after.py index 1a98a9cfc6c4..71d467c1cb4d 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncClassInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncClassInPy36_after.py @@ -1,2 +1,2 @@ -class a(object): +class A_NEW_NAME(object): pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncFunctionInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncFunctionInPy36_after.py index 690f3c163030..56255fa9e6e7 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncFunctionInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncFunctionInPy36_after.py @@ -1,2 +1,2 @@ -def a(): +def A_NEW_NAME(): pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncVariableInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncVariableInPy36_after.py index d25d49e0ffc3..32b9c25e607f 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncVariableInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAsyncVariableInPy36_after.py @@ -1 +1 @@ -a = 1 \ No newline at end of file +A_NEW_NAME = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitClassInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitClassInPy36_after.py index 1a98a9cfc6c4..71d467c1cb4d 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitClassInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitClassInPy36_after.py @@ -1,2 +1,2 @@ -class a(object): +class A_NEW_NAME(object): pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitFunctionInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitFunctionInPy36_after.py index 690f3c163030..56255fa9e6e7 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitFunctionInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitFunctionInPy36_after.py @@ -1,2 +1,2 @@ -def a(): +def A_NEW_NAME(): pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitVariableInPy36_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitVariableInPy36_after.py index d25d49e0ffc3..32b9c25e607f 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitVariableInPy36_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameAwaitVariableInPy36_after.py @@ -1 +1 @@ -a = 1 \ No newline at end of file +A_NEW_NAME = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment.py new file mode 100644 index 000000000000..556e829d88b3 --- /dev/null +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment.py @@ -0,0 +1,4 @@ +a = """ +def f(): + A = 1 +""" \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment_after.py new file mode 100644 index 000000000000..795395c84203 --- /dev/null +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/renameInInjectedFragment_after.py @@ -0,0 +1,4 @@ +a = """ +def f(): + a = 1 +""" \ No newline at end of file diff --git a/python/testData/quickFixes/PyRenameElementQuickFixTest/shadowingBuiltins_after.py b/python/testData/quickFixes/PyRenameElementQuickFixTest/shadowingBuiltins_after.py index d8335e594f59..81548e327368 100644 --- a/python/testData/quickFixes/PyRenameElementQuickFixTest/shadowingBuiltins_after.py +++ b/python/testData/quickFixes/PyRenameElementQuickFixTest/shadowingBuiltins_after.py @@ -1,2 +1,2 @@ def foo(): - a = 10 \ No newline at end of file + A_NEW_NAME = 10 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index c6f36a0306d6..ff76b67d37d8 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -14,6 +14,7 @@ import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.inspections.*; import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.quickFixes.PyRenameElementQuickFixTest; import org.intellij.lang.regexp.inspection.RedundantEscapeInspection; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,7 @@ public class PyQuickFixTest extends PyTestCase { super.setUp(); InspectionProfileImpl.INIT_INSPECTIONS = true; myFixture.setCaresAboutInjection(false); + PyRenameElementQuickFixTest.registerTestNameSuggestionProvider(getTestRootDisposable()); } @Override diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyRenameElementQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyRenameElementQuickFixTest.java index 50c04561455d..939e10ede562 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/PyRenameElementQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyRenameElementQuickFixTest.java @@ -15,22 +15,88 @@ */ package com.jetbrains.python.quickFixes; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.lang.injection.MultiHostInjector; +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLanguageInjectionHost; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.refactoring.rename.NameSuggestionProvider; +import com.intellij.refactoring.rename.PreferrableNameSuggestionProvider; +import com.intellij.testFramework.PlatformTestUtil; import com.intellij.testFramework.TestDataPath; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyQuickFixTestCase; +import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.inspections.PyCompatibilityInspection; import com.jetbrains.python.inspections.PyPep8NamingInspection; import com.jetbrains.python.inspections.PyProtectedMemberInspection; import com.jetbrains.python.inspections.PyShadowingBuiltinsInspection; import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyStringLiteralExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Set; @TestDataPath("$CONTENT_ROOT/../testData//quickFixes/RenameElementQuickFixTest/") public class PyRenameElementQuickFixTest extends PyQuickFixTestCase { + // The value renamed element will have after quick fix rename + public static final String RENAME_RESULT_BY_RENAME_HANDLER = "A_NEW_NAME"; + + @Override + public void setUp() throws Exception { + super.setUp(); + registerTestNameSuggestionProvider(getTestRootDisposable()); + } + + /** + * Registers {@link NameSuggestionProvider} to provide the name that will replace the one under the caret during the test. + * + * @see PyRenameElementQuickFixTest.RENAME_RESULT_BY_RENAME_HANDLER + */ + public static void registerTestNameSuggestionProvider(Disposable disposable) { + PlatformTestUtil.registerExtension( + Extensions.getRootArea(), NameSuggestionProvider.EP_NAME, createTestNameSuggestionProvider(), disposable); + } + + /** + * Create {@link NameSuggestionProvider} that will provide {@link RENAME_RESULT_BY_RENAME_HANDLER} to the list of variants. + * + * Note, that according to the renaming logic the {@link RENAME_RESULT_BY_RENAME_HANDLER} should lexicographically precede all other name suggestions to + * appear in testing result. + * + * @see com.intellij.refactoring.rename.PsiElementRenameHandler#rename(PsiElement, Project, PsiElement, Editor, String + */ + @NotNull + private static NameSuggestionProvider createTestNameSuggestionProvider() { + return new PreferrableNameSuggestionProvider() { + @Nullable + @Override + public SuggestedNameInfo getSuggestedNames(PsiElement element, @Nullable PsiElement nameSuggestionContext, Set result) { + result.add(RENAME_RESULT_BY_RENAME_HANDLER); + return new SuggestedNameInfo(new String[]{RENAME_RESULT_BY_RENAME_HANDLER}) {}; + } + }; + } + public void testProtectedMember() { doQuickFixTest(PyProtectedMemberInspection.class, PyBundle.message("QFIX.NAME.rename.element")); } + /** + * Note that the renaming is performed by {@link com.intellij.refactoring.rename.inplace.VariableInplaceRenamer} + * and therefore the result is different from {@link RENAME_RESULT_BY_RENAME_HANDLER}. + */ public void testPep8() { doQuickFixTest(PyPep8NamingInspection.class, PyBundle.message("QFIX.NAME.rename.element")); } @@ -82,4 +148,49 @@ public class PyRenameElementQuickFixTest extends PyQuickFixTestCase { runWithLanguageLevel(LanguageLevel.PYTHON36, () -> doQuickFixTest(PyCompatibilityInspection.class, PyBundle.message("QFIX.NAME.rename.element"))); } + + // PY-30827 + public void testRenameInInjectedFragment() { + testInInjectedLanguageFragment( + () -> doQuickFixTest(PyPep8NamingInspection.class, PyBundle.message("QFIX.NAME.rename.element"))); + } + + /** + * Test with language injection. + * + * Python is injected into string literal before running test code. + */ + private void testInInjectedLanguageFragment(@NotNull Runnable runnable) { + Disposable testDisposable = new Disposable() { + @Override + public void dispose() {} + }; + injectPythonLanguage(testDisposable); + runnable.run(); + Disposer.dispose(testDisposable); + } + + /** + * @param testDisposable {@link Disposable} to trigger language injector disposal + */ + private void injectPythonLanguage(Disposable testDisposable) { + final InjectedLanguageManager manager = InjectedLanguageManager.getInstance(myFixture.getProject()); + + MultiHostInjector multiHostInjector = new MultiHostInjector() { + @Override + public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) { + TextRange range = new TextRange(4, context.getTextLength() - 4); + registrar.startInjecting(PythonLanguage.INSTANCE) + .addPlace(null, null, (PsiLanguageInjectionHost)context, range) + .doneInjecting(); + + } + @NotNull + @Override + public List> elementsToInjectIn() { + return ContainerUtil.newArrayList(PyStringLiteralExpression.class); + } + }; + manager.registerMultiHostInjector(multiHostInjector, testDisposable); + } }