PY-30762: Rename param usages when fixture (to which param has reference) is renamed

Param usage has reference to PyNamedParam.
                    PyNamedParam has reference to fixture.

                    When we rename fixture, PyNamedParam is also renamed because
                    PyNamedParam#isReferenceTo(Fixture) == true, but ParamUsage#isReferenceTo(Fixture) is not.

                    We use EP to fix it.
This commit is contained in:
Ilya.Kazakevich
2018-07-16 22:25:58 +03:00
parent 083cc1a529
commit 83b6278502
7 changed files with 58 additions and 9 deletions
@@ -650,6 +650,7 @@
</extensions>
<extensionPoints>
<extensionPoint qualifiedName="Pythonid.pyReferenceCustomTargetChecker" interface="com.jetbrains.python.psi.impl.references.PyReferenceCustomTargetChecker"/>
<extensionPoint qualifiedName="Pythonid.pyTestFixtureExtension" interface="com.jetbrains.python.testing.pyTestFixtures.PyTestFixtureExtension"/>
<extensionPoint qualifiedName="Pythonid.pyTestConfigurationSelector" interface="com.jetbrains.python.testing.PyTestConfigurationSelector"/>
<extensionPoint qualifiedName="Pythonid.pythonDocumentationQuickInfoProvider" interface="com.jetbrains.python.documentation.PythonDocumentationQuickInfoProvider"/>
@@ -703,6 +704,7 @@
</extensionPoints>
<extensions defaultExtensionNs="Pythonid">
<pyReferenceCustomTargetChecker implementation="com.jetbrains.python.testing.pyTestFixtures.PyTestFixtureTargetChecker"/>
<pySuperMethodsSearch implementation="com.jetbrains.python.psi.search.PySuperMethodsSearchExecutor"/>
<pyClassInheritorsSearch implementation="com.jetbrains.python.psi.search.PyClassInheritorsSearchExecutor"/>
<pyOverridingMethodsSearch implementation="com.jetbrains.python.psi.search.PyOverridingMethodsSearchExecutor"/>
@@ -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<PyReferenceCustomTargetChecker>("Pythonid.pyReferenceCustomTargetChecker")
fun isReferenceTo(reference: PsiReference, to: PsiElement) = EP_NAME.extensions.firstOrNull { it.isReferenceTo(reference, to) } != null
}
fun isReferenceTo(reference: PsiReference, to: PsiElement): Boolean
}
@@ -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) {
@@ -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<PyTestFixture> {
// 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
@@ -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()
}
@@ -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<PyType>? {
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<PsiReference> {
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))
}
}
@@ -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<PyTestFixtureReference>().firstOrNull() ?: return false
return ref.isReferenceTo(to)
}
return false
}
}