diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java index c2e0417036dd..b2c3b135ed8b 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibClassMembersProvider.java @@ -4,19 +4,17 @@ package com.jetbrains.python.codeInsight.stdlib; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; import com.intellij.util.containers.ContainerUtil; -import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCustomMember; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.resolve.QualifiedNameFinder; -import com.jetbrains.python.psi.types.PyClassMembersProviderBase; -import com.jetbrains.python.psi.types.PyClassType; -import com.jetbrains.python.psi.types.PyFunctionType; -import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * @author yole @@ -27,7 +25,7 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { private static final Key> SOCKET_MEMBERS_KEY = Key.create("socket.members"); @NotNull - private static final List MOCK_PATCH_MEMBERS = calcMockPatchMembers(); + public static final List MOCK_PATCH_MEMBERS = calcMockPatchMembers(); @NotNull @Override @@ -45,8 +43,8 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { } if (location instanceof PyReferenceExpression) { - final PyCallable mockPatchCallable = mockPatchCallable(classType, ((PyReferenceExpression)location).getQualifier(), context); - if (mockPatchCallable != null) { + final PyExpression qualifier = ((PyReferenceExpression)location).getQualifier(); + if (qualifier instanceof PyReferenceExpression && referenceToMockPatch((PyReferenceExpression)qualifier, context)) { return MOCK_PATCH_MEMBERS; } } @@ -56,8 +54,8 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { @Override public PsiElement resolveMember(PyClassType clazz, String name, @Nullable PsiElement location, @NotNull PyResolveContext resolveContext) { - final PyCallable mockPatchCallable = mockPatchCallable(clazz, location, resolveContext.getTypeEvalContext()); - if (mockPatchCallable != null && location!= null) { + final TypeEvalContext context = resolveContext.getTypeEvalContext(); + if (location instanceof PyReferenceExpression && referenceToMockPatch((PyReferenceExpression)location, context)) { for (PyCustomMember member : MOCK_PATCH_MEMBERS) { if (name.equals(member.getName())) { return member.resolve(location, resolveContext); @@ -68,6 +66,14 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { return super.resolveMember(clazz, name, location, resolveContext); } + 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()); + } + return false; + } + private static List calcSocketMembers(PyFile socketFile) { final List result = new ArrayList<>(); addMethodsFromAttr(socketFile, result, "_socketmethods"); @@ -75,21 +81,6 @@ public class PyStdlibClassMembersProvider extends PyClassMembersProviderBase { return result; } - @Nullable - private static PyCallable mockPatchCallable(@NotNull PyClassType classType, @Nullable PsiElement location, @NotNull TypeEvalContext context) { - if (!PyNames.TYPES_FUNCTION_TYPE.equals(classType.getClassQName())) { - return null; - } - - return Optional - .ofNullable(PyUtil.as(location, PyReferenceExpression.class)) - .map(context::getType) - .map(qualifierType -> PyUtil.as(qualifierType, PyFunctionType.class)) - .map(PyFunctionType::getCallable) - .filter(callable -> "unittest.mock.patch".equals(QualifiedNameFinder.getQualifiedName(callable))) - .orElse(null); - } - @NotNull private static List calcMockPatchMembers() { final String[] members = new String[]{"object", "dict", "multiple", "stopall", "TEST_PREFIX"}; diff --git a/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt b/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt index 20ef6c8f8f3b..181a090d2aad 100644 --- a/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt +++ b/python/src/com/jetbrains/python/inspections/stdlib/PyStdlibInspectionExtension.kt @@ -1,24 +1,14 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.inspections.stdlib +import com.intellij.psi.PsiReference import com.jetbrains.python.PyNames import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType +import com.jetbrains.python.codeInsight.stdlib.PyStdlibClassMembersProvider import com.jetbrains.python.inspections.PyInspectionExtension +import com.jetbrains.python.psi.PyElement import com.jetbrains.python.psi.PyFunction +import com.jetbrains.python.psi.PyReferenceExpression import com.jetbrains.python.psi.types.PyClassLikeType import com.jetbrains.python.psi.types.PyType import com.jetbrains.python.psi.types.TypeEvalContext @@ -37,4 +27,16 @@ class PyStdlibInspectionExtension : PyInspectionExtension() { return false } + + override fun ignoreUnresolvedReference(node: PyElement, reference: PsiReference): Boolean { + if (node is PyReferenceExpression && node.isQualified) { + val qualifier = node.qualifier + if (qualifier is PyReferenceExpression) { + return PyStdlibClassMembersProvider.referenceToMockPatch(qualifier, TypeEvalContext.codeInsightFallback(qualifier.project)) && + PyStdlibClassMembersProvider.MOCK_PATCH_MEMBERS.find { it.name == node.name } != null + } + } + + return false + } } \ No newline at end of file