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