From c986901a1712430b617b5406e4d2fe444d13d849 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 12 Feb 2018 18:09:09 +0300 Subject: [PATCH] Support mock.patch members in Python 2 (PY-23632) --- .../stdlib/PyStdlibClassMembersProvider.java | 24 ++++++++++++------- .../stdlib/PyStdlibInspectionExtension.kt | 2 +- .../completion/mockPatchObject1Py2/a.after.py | 3 +++ .../completion/mockPatchObject1Py2/a.py | 3 +++ .../mockPatchObject1Py2/mock/__init__.py | 1 + .../mockPatchObject1Py2/mock/mock.py | 4 ++++ .../completion/mockPatchObject2Py2/a.after.py | 3 +++ .../completion/mockPatchObject2Py2/a.py | 3 +++ .../mockPatchObject2Py2/mock/__init__.py | 1 + .../mockPatchObject2Py2/mock/mock.py | 4 ++++ .../MockPatchObject/a.py | 7 ++++++ .../MockPatchObject/mock/__init__.py | 1 + .../MockPatchObject/mock/mock.py | 4 ++++ .../python/PythonCompletionTest.java | 10 ++++++++ .../PyUnresolvedReferencesInspectionTest.java | 5 ++++ 15 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 python/testData/completion/mockPatchObject1Py2/a.after.py create mode 100644 python/testData/completion/mockPatchObject1Py2/a.py create mode 100644 python/testData/completion/mockPatchObject1Py2/mock/__init__.py create mode 100644 python/testData/completion/mockPatchObject1Py2/mock/mock.py create mode 100644 python/testData/completion/mockPatchObject2Py2/a.after.py create mode 100644 python/testData/completion/mockPatchObject2Py2/a.py create mode 100644 python/testData/completion/mockPatchObject2Py2/mock/__init__.py create mode 100644 python/testData/completion/mockPatchObject2Py2/mock/mock.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/a.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/__init__.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/mock.py diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java index eacb1a84503a..bd9ed33e951d 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java @@ -3,6 +3,7 @@ package com.jetbrains.python.codeInsight.stdlib; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.QualifiedName; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.codeInsight.PyCustomMember; import com.jetbrains.python.psi.*; @@ -24,9 +25,6 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { @NotNull private static final Key> SOCKET_MEMBERS_KEY = Key.create("socket.members"); - @NotNull - public static final List MOCK_PATCH_MEMBERS = calcMockPatchMembers(); - @NotNull @Override public Collection getMembers(PyClassType classType, PsiElement location, @NotNull TypeEvalContext context) { @@ -45,7 +43,7 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { if (location instanceof PyReferenceExpression) { final PyExpression qualifier = ((PyReferenceExpression)location).getQualifier(); if (qualifier instanceof PyReferenceExpression && referenceToMockPatch((PyReferenceExpression)qualifier, context)) { - return MOCK_PATCH_MEMBERS; + return calcMockPatchMembers(qualifier); } } @@ -60,7 +58,7 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { @NotNull PyResolveContext resolveContext) { final TypeEvalContext context = resolveContext.getTypeEvalContext(); if (location instanceof PyReferenceExpression && referenceToMockPatch((PyReferenceExpression)location, context)) { - for (PyCustomMember member : MOCK_PATCH_MEMBERS) { + for (PyCustomMember member : calcMockPatchMembers(location)) { if (name.equals(member.getName())) { return member.resolve(location, resolveContext); } @@ -73,7 +71,8 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { public static boolean referenceToMockPatch(@NotNull PyReferenceExpression referenceExpression, @NotNull TypeEvalContext context) { final PyType type = context.getType(referenceExpression); if (type instanceof PyFunctionType) { - return "unittest.mock.patch".equals(((PyFunctionType)type).getCallable().getQualifiedName()); + final String callableQName = ((PyFunctionType)type).getCallable().getQualifiedName(); + return mockPatchQName(referenceExpression).toString().equals(callableQName); } return false; } @@ -86,13 +85,20 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { } @NotNull - private static List calcMockPatchMembers() { - final String[] members = new String[]{"object", "dict", "multiple", "stopall", "TEST_PREFIX"}; - final String moduleQName = "unittest.mock"; + public static List calcMockPatchMembers(@NotNull PsiElement anchor) { + final String[] members = {"object", "dict", "multiple", "stopall", "TEST_PREFIX"}; + final String moduleQName = mockPatchQName(anchor).removeLastComponent().toString(); return ContainerUtil.map(members, member -> new PyCustomMember(member).resolvesTo(moduleQName).toAssignment("patch." + member)); } + @NotNull + private static QualifiedName mockPatchQName(@NotNull PsiElement anchor) { + return LanguageLevel.forElement(anchor).isPython2() + ? QualifiedName.fromComponents("mock", "mock", "patch") + : QualifiedName.fromComponents("unittest", "mock", "patch"); + } + private static void addMethodsFromAttr(PyFile socketFile, List result, final String attrName) { final PyTargetExpression socketMethods = socketFile.findTopLevelAttribute(attrName); if (socketMethods != null) { diff --git a/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt b/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt index a5857020e1fa..bcc1cb3b968c 100644 --- a/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt +++ b/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt @@ -38,7 +38,7 @@ class PyStdlibInspectionExtension : PyInspectionExtension() { val qualifier = node.qualifier if (qualifier is PyReferenceExpression) { return PyStdlibClassMembersProvider.referenceToMockPatch(qualifier, context) && - PyStdlibClassMembersProvider.MOCK_PATCH_MEMBERS.find { it.name == node.name } != null + PyStdlibClassMembersProvider.calcMockPatchMembers(qualifier).any { it.name == node.name } } } diff --git a/python/testData/completion/mockPatchObject1Py2/a.after.py b/python/testData/completion/mockPatchObject1Py2/a.after.py new file mode 100644 index 000000000000..ebcd229e1c59 --- /dev/null +++ b/python/testData/completion/mockPatchObject1Py2/a.after.py @@ -0,0 +1,3 @@ +from mock import patch + +patch.object \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject1Py2/a.py b/python/testData/completion/mockPatchObject1Py2/a.py new file mode 100644 index 000000000000..d223001f2e96 --- /dev/null +++ b/python/testData/completion/mockPatchObject1Py2/a.py @@ -0,0 +1,3 @@ +from mock import patch + +patch.obj \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject1Py2/mock/__init__.py b/python/testData/completion/mockPatchObject1Py2/mock/__init__.py new file mode 100644 index 000000000000..fb68a0b45967 --- /dev/null +++ b/python/testData/completion/mockPatchObject1Py2/mock/__init__.py @@ -0,0 +1 @@ +from mock.mock import * \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject1Py2/mock/mock.py b/python/testData/completion/mockPatchObject1Py2/mock/mock.py new file mode 100644 index 000000000000..c013a9a406d0 --- /dev/null +++ b/python/testData/completion/mockPatchObject1Py2/mock/mock.py @@ -0,0 +1,4 @@ +def patch(): + pass + +patch.object = None \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject2Py2/a.after.py b/python/testData/completion/mockPatchObject2Py2/a.after.py new file mode 100644 index 000000000000..84d8c3d68192 --- /dev/null +++ b/python/testData/completion/mockPatchObject2Py2/a.after.py @@ -0,0 +1,3 @@ +import mock + +mock.patch.object \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject2Py2/a.py b/python/testData/completion/mockPatchObject2Py2/a.py new file mode 100644 index 000000000000..55f636e3cbb1 --- /dev/null +++ b/python/testData/completion/mockPatchObject2Py2/a.py @@ -0,0 +1,3 @@ +import mock + +mock.patch.obj \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject2Py2/mock/__init__.py b/python/testData/completion/mockPatchObject2Py2/mock/__init__.py new file mode 100644 index 000000000000..fb68a0b45967 --- /dev/null +++ b/python/testData/completion/mockPatchObject2Py2/mock/__init__.py @@ -0,0 +1 @@ +from mock.mock import * \ No newline at end of file diff --git a/python/testData/completion/mockPatchObject2Py2/mock/mock.py b/python/testData/completion/mockPatchObject2Py2/mock/mock.py new file mode 100644 index 000000000000..c013a9a406d0 --- /dev/null +++ b/python/testData/completion/mockPatchObject2Py2/mock/mock.py @@ -0,0 +1,4 @@ +def patch(): + pass + +patch.object = None \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/a.py new file mode 100644 index 000000000000..7e047beb9566 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/a.py @@ -0,0 +1,7 @@ +from mock import patch + +print(patch.object) + +import mock + +print(mock.patch.object) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/__init__.py new file mode 100644 index 000000000000..fb68a0b45967 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/__init__.py @@ -0,0 +1 @@ +from mock.mock import * \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/mock.py b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/mock.py new file mode 100644 index 000000000000..c013a9a406d0 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/MockPatchObject/mock/mock.py @@ -0,0 +1,4 @@ +def patch(): + pass + +patch.object = None \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 00001deb6848..35f3716e09d0 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1235,6 +1235,16 @@ public class PythonCompletionTest extends PyTestCase { ); } + // PY-23632 + public void testMockPatchObject1Py2() { + doMultiFileTest(); + } + + // PY-23632 + public void testMockPatchObject2Py2() { + doMultiFileTest(); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index f7bb33089209..fdd0c5a768e8 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -681,6 +681,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doMultiFileTest(); } + // PY-23632 + public void testMockPatchObject() { + doMultiFileTest(); + } + @NotNull @Override protected Class getInspectionClass() {