diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml
index 61ec85b1a0b8..848da83a1f57 100644
--- a/python/src/META-INF/python-core-common.xml
+++ b/python/src/META-INF/python-core-common.xml
@@ -650,6 +650,7 @@
+
@@ -703,6 +704,7 @@
+
diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceCustomTargetChecker.kt b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceCustomTargetChecker.kt
new file mode 100644
index 000000000000..e4dded3f9a06
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceCustomTargetChecker.kt
@@ -0,0 +1,18 @@
+// Copyright 2000-2018 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.psi.impl.references
+
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiReference
+
+/**
+ * EP to check if some reference points to some element
+ */
+interface PyReferenceCustomTargetChecker {
+ companion object {
+ val EP_NAME = ExtensionPointName.create("Pythonid.pyReferenceCustomTargetChecker")
+ fun isReferenceTo(reference: PsiReference, to: PsiElement) = EP_NAME.extensions.firstOrNull { it.isReferenceTo(reference, to) } != null
+ }
+
+ fun isReferenceTo(reference: PsiReference, to: PsiElement): Boolean
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java
index 97876e0ae20a..98ab7088c307 100644
--- a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java
@@ -570,7 +570,8 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
}
}
}
- return false;
+
+ return PyReferenceCustomTargetChecker.Companion.isReferenceTo(this, element);
}
private boolean resolvesToSameLocal(PsiElement element, String elementName, ScopeOwner ourScopeOwner, ScopeOwner theirScopeOwner) {
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt
index 4506711d8e8f..c61200b0aef4 100644
--- a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt
@@ -41,9 +41,9 @@ internal fun getFixture(element: PyNamedParameter, typeEvalContext: TypeEvalCont
fun PyNamedParameter.isFixture(typeEvalContext: TypeEvalContext) = getFixture(this, typeEvalContext) != null
/**
- * @return Boolean is function decorated as fixture
+ * @return Boolean is function decorated as fixture or marked so by EP
*/
-internal fun PyFunction.isFixture() = decoratorList?.findDecorator(decoratorName) != null
+internal fun PyFunction.isFixture() = decoratorList?.findDecorator(decoratorName) != null || isCustomFixture()
/**
@@ -92,7 +92,7 @@ private val pyTestName = PyTestFrameworkService.getSdkReadableNameByFramework(Py
internal fun getFixtures(module: Module, forWhat: PyFunction, typeEvalContext: TypeEvalContext): List {
// Fixtures could be used only by test functions or other fixtures.
val fixture = forWhat.isFixture()
- val pyTestEnabled = TestRunnerService.getInstance(module).projectConfiguration == pyTestName
+ val pyTestEnabled = isPyTestEnabled(module)
return if (
fixture ||
(pyTestEnabled && isTestElement(forWhat, ThreeState.NO, typeEvalContext)) ||
@@ -107,4 +107,7 @@ internal fun getFixtures(module: Module, forWhat: PyFunction, typeEvalContext: T
else emptyList()
}
+internal fun isPyTestEnabled(module: Module) =
+ TestRunnerService.getInstance(module).projectConfiguration == pyTestName
+
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt
index 4b86c12f4b3d..cd03d80fac6a 100644
--- a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt
@@ -14,6 +14,5 @@ object PyTestFixtureInspectionExtension : PyInspectionExtension() {
override fun ignoreUnused(local: PsiElement, evalContext: TypeEvalContext) =
local is PyNamedParameter && local.isFixture(evalContext)
- override fun ignoreShadowed(element: PsiElement) = element is PyFunction
- && (element.isFixture() || element.isCustomFixture())
+ override fun ignoreShadowed(element: PsiElement) = element is PyFunction && element.isFixture()
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureReferenceContributor.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureReferenceContributor.kt
index ee670482e986..f4adf348fe80 100644
--- a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureReferenceContributor.kt
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureReferenceContributor.kt
@@ -13,7 +13,7 @@ import com.jetbrains.python.psi.types.PyType
import com.jetbrains.python.psi.types.PyTypeProviderBase
import com.jetbrains.python.psi.types.TypeEvalContext
-private class PyTextFixtureReference(namedParameter: PyNamedParameter, fixture: PyTestFixture) : BaseReference(namedParameter) {
+class PyTestFixtureReference(namedParameter: PyNamedParameter, fixture: PyTestFixture) : BaseReference(namedParameter) {
private val functionRef = SmartPointerManager.createPointer(fixture.function)
private val resolveRef = SmartPointerManager.createPointer(fixture.resolveTarget)
@@ -33,7 +33,7 @@ private class PyTextFixtureReference(namedParameter: PyNamedParameter, fixture:
object PyTextFixtureTypeProvider : PyTypeProviderBase() {
override fun getReferenceType(referenceTarget: PsiElement, context: TypeEvalContext, anchor: PsiElement?): Ref? {
val param = referenceTarget as? PyNamedParameter ?: return null
- val fixtureFunc = param.references.filterIsInstance(PyTextFixtureReference::class.java).firstOrNull()?.getFunction() ?: return null
+ val fixtureFunc = param.references.filterIsInstance(PyTestFixtureReference::class.java).firstOrNull()?.getFunction() ?: return null
return context.getReturnType(fixtureFunc)?.let { Ref(it) }
}
@@ -43,7 +43,7 @@ private object PyTestReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array {
val namedParam = element as? PyNamedParameter ?: return emptyArray()
val fixture = getFixture(namedParam, TypeEvalContext.codeAnalysis(element.project, element.containingFile)) ?: return emptyArray()
- return arrayOf(PyTextFixtureReference(namedParam, fixture))
+ return arrayOf(PyTestFixtureReference(namedParam, fixture))
}
}
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureTargetChecker.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureTargetChecker.kt
new file mode 100644
index 000000000000..5076da993bb4
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureTargetChecker.kt
@@ -0,0 +1,26 @@
+// Copyright 2000-2018 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.testing.pyTestFixtures
+
+import com.intellij.openapi.module.ModuleUtilCore
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiReference
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyNamedParameter
+import com.jetbrains.python.psi.impl.references.PyReferenceCustomTargetChecker
+
+class PyTestFixtureTargetChecker : PyReferenceCustomTargetChecker {
+ override fun isReferenceTo(reference: PsiReference, to: PsiElement): Boolean {
+ val function = to as? PyFunction ?: return false
+ val module = ModuleUtilCore.findModuleForPsiElement(to) ?: return false
+
+ // reference is reference from param usage to param
+ // param has reference to fixture
+
+ if (function.isFixture() && isPyTestEnabled(module)) {
+ val parameter = reference.resolve() as? PyNamedParameter ?: return false
+ val ref = parameter.references.filterIsInstance().firstOrNull() ?: return false
+ return ref.isReferenceTo(to)
+ }
+ return false
+ }
+}
\ No newline at end of file