diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml
index e6d5841356f1..1bdfaca8b321 100644
--- a/python/intellij.python.community.impl.iml
+++ b/python/intellij.python.community.impl.iml
@@ -37,5 +37,6 @@
+
\ No newline at end of file
diff --git a/python/psi-api/src/com/jetbrains/python/inspections/PyInspectionExtension.java b/python/psi-api/src/com/jetbrains/python/inspections/PyInspectionExtension.java
index 8bfc29072853..ac14d7ea6a9f 100644
--- a/python/psi-api/src/com/jetbrains/python/inspections/PyInspectionExtension.java
+++ b/python/psi-api/src/com/jetbrains/python/inspections/PyInspectionExtension.java
@@ -21,6 +21,10 @@ public abstract class PyInspectionExtension {
return false;
}
+ public boolean ignoreShadowed(@NotNull final PsiElement element) {
+ return false;
+ }
+
public boolean ignoreMissingDocstring(PyDocStringOwner docStringOwner) {
return false;
}
diff --git a/python/resources/inspectionDescriptions/PyParametrizedArgumentInspection.html b/python/resources/inspectionDescriptions/PyParametrizedArgumentInspection.html
new file mode 100644
index 000000000000..3ae2a0d86dfa
--- /dev/null
+++ b/python/resources/inspectionDescriptions/PyParametrizedArgumentInspection.html
@@ -0,0 +1,5 @@
+
+
+Test function, decorated with @pytest.mark.parametrize, must have arguments to accept parameters from decorator
+
+
diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml
index 3cd2b0367573..c2f3176fc4b7 100644
--- a/python/src/META-INF/python-core-common.xml
+++ b/python/src/META-INF/python-core-common.xml
@@ -349,7 +349,10 @@
-
+
@@ -533,6 +536,7 @@
+
@@ -716,6 +720,7 @@
+
@@ -735,6 +740,8 @@
+
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 8e3315020156..c625ab37e5bc 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -603,6 +603,10 @@ INSP.NAME.global.$0.undefined=Global variable ''{0}'' is undefined at the module
INSP.NAME.assignment.to.loop.or.with.parameter.display.name=Assignment to 'for' loop or 'with' statement parameter
INSP.NAME.assignment.to.loop.or.with.parameter.display.message=Variable ''{0}'' already declared in ''for'' loop or ''with'' statement above
+
+# PyParametrizedArgumentInspection
+INSP.NAME.pytest-parametrized=Checks that functions decorated by pytest parametrize have correct arguments
+
# Refactoring
refactoring.will.not.be.accessible=Member, you are trying to move depends on ''{0}'' which will not be accessible after this refactoring
refactoring.error.file.exists=File "{0}" already exists
diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
index 80d925065f37..b337b3f7b74d 100644
--- a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
@@ -34,6 +34,8 @@ import com.jetbrains.python.psi.resolve.PyResolveUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Arrays;
+
/**
* Warns about shadowing names defined in outer scopes.
*
@@ -111,6 +113,10 @@ public class PyShadowingNamesInspection extends PyInspection {
if (scope.isGlobal(name) || scope.isNonlocal(name)) {
return;
}
+ if (Arrays.stream(PyInspectionExtension.EP_NAME.getExtensions())
+ .anyMatch(o -> o.ignoreShadowed(resolved))) {
+ return;
+ }
registerProblem(problemElement, String.format("Shadows name '%s' from outer scope", name),
ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix());
return;
diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
index 63b70eaacc3c..0333c4347e4f 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
@@ -49,8 +49,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase im
@Override
@Nullable
- public PyFunction getTarget() {
- return PsiTreeUtil.getParentOfType(this, PyFunction.class);
+ public final PyFunction getTarget() {
+ return PsiTreeUtil.getStubOrPsiParentOfType(this, PyFunction.class);
}
@Override
diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
index 835f1ee9569e..45c8a3737e12 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
@@ -20,9 +20,13 @@ import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
+import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.stubs.IStubElementType;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
@@ -62,6 +66,14 @@ public class PyNamedParameterImpl extends PyBaseElementImpl CachedValueProvider.Result.create(
+ ReferenceProvidersRegistry.getReferencesFromProviders(this, PsiReferenceService.Hints.NO_HINTS),
+ PsiModificationTracker.MODIFICATION_COUNT));
+ }
+
@Nullable
@Override
public String getName() {
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt
new file mode 100644
index 000000000000..0f12aa579301
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt
@@ -0,0 +1,46 @@
+// 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.Module
+import com.intellij.openapi.module.ModuleUtilCore
+import com.intellij.psi.stubs.StubIndex
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.ThreeState
+import com.jetbrains.python.psi.PyDecorator
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyNamedParameter
+import com.jetbrains.python.psi.stubs.PyDecoratorStubIndex
+import com.jetbrains.python.psi.types.TypeEvalContext
+import com.jetbrains.python.testing.isTestElement
+
+const val decoratorName = "pytest.fixture"
+
+/**
+ * If named parameter has fixture -- return it
+ */
+internal fun getFixture(element: PyNamedParameter, typeEvalContext: TypeEvalContext): PyFunction? {
+ val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return null
+ val func = PsiTreeUtil.getParentOfType(element, PyFunction::class.java) ?: return null
+ if (!isTestElement(func, ThreeState.NO, typeEvalContext)) {
+ return null
+ }
+ return getFixtures(module).firstOrNull { o -> o.name == element.name }
+}
+
+/**
+ * @return Boolean If named parameter has fixture or not
+ */
+internal fun hasFixture(element: PyNamedParameter, typeEvalContext: TypeEvalContext) = getFixture(element, typeEvalContext) != null
+
+/**
+ * @return Boolean is function decorated as fixture
+ */
+internal fun PyFunction.isFixture() = decoratorList?.findDecorator(decoratorName) != null
+/**
+ * @return List all py.test fixtures in project
+ */
+internal fun getFixtures(module: Module) =
+ StubIndex.getElements(PyDecoratorStubIndex.KEY, decoratorName, module.project,
+ module.moduleContentScope,
+ PyDecorator::class.java).mapNotNull(PyDecorator::getTarget)
+
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt
new file mode 100644
index 000000000000..0824a744d2a7
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureInspectionExtension.kt
@@ -0,0 +1,19 @@
+// 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.psi.PsiElement
+import com.jetbrains.python.inspections.PyInspectionExtension
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyNamedParameter
+import com.jetbrains.python.psi.types.TypeEvalContext
+
+/**
+ * fixture-based parameters should be skipped by inspection
+ */
+object PyTestFixtureInspectionExtension : PyInspectionExtension() {
+ override fun ignoreUnused(local: PsiElement) = local is PyNamedParameter
+ && hasFixture(local, TypeEvalContext.codeAnalysis(local.project,
+ local.containingFile))
+
+ 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
new file mode 100644
index 000000000000..e7abd3ec9697
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixtureReferenceContributor.kt
@@ -0,0 +1,55 @@
+// 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.util.Ref
+import com.intellij.patterns.PlatformPatterns
+import com.intellij.psi.*
+import com.intellij.util.ProcessingContext
+import com.jetbrains.python.BaseReference
+import com.jetbrains.python.psi.PyElementGenerator
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyNamedParameter
+import com.jetbrains.python.psi.PyParameter
+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: PyFunction) : BaseReference(namedParameter) {
+ private val fixtureRef = SmartPointerManager.createPointer(fixture)
+
+ override fun resolve() = fixtureRef.element
+
+ override fun getVariants() = emptyArray()
+
+ override fun isSoft() = true
+
+ override fun handleElementRename(newElementName: String) = myElement.replace(
+ PyElementGenerator.getInstance(myElement.project).createParameter(newElementName))!!
+}
+
+
+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()?.resolve() ?: return null
+ return context.getReturnType(fixtureFunc)?.let { Ref(it) }
+
+ }
+}
+
+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))
+ }
+}
+
+object PyTestFixtureReferenceContributor : PsiReferenceContributor() {
+
+ override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
+ registrar.registerReferenceProvider(PlatformPatterns.psiElement(PyParameter::class.java), PyTestReferenceProvider,
+ PsiReferenceRegistrar.HIGHER_PRIORITY)
+ }
+
+}
diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/README.md b/python/src/com/jetbrains/python/testing/pyTestFixtures/README.md
new file mode 100644
index 000000000000..20ad2883555d
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/README.md
@@ -0,0 +1,16 @@
+https://docs.pytest.org/en/latest/fixture.html
+
+@pytest.fixture
+def foo():pass
+
+Somehwere in the code
+
+def test_test(foo):pass
+
+"foo" is fixture here: pytest calls foo() and provides its return value.
+PyCharm:
+* Disables "unused" and "hides" inspections
+* Provides reference (navigate, find usage, rename etc)
+* Provides type for foo
+
+
diff --git a/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedArgumentInspection.kt b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedArgumentInspection.kt
new file mode 100644
index 000000000000..9a792b1eebb4
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedArgumentInspection.kt
@@ -0,0 +1,39 @@
+// 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.pyTestParametrized
+
+import com.intellij.codeInspection.LocalInspectionToolSession
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementVisitor
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.inspections.PyInspection
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyParameter
+
+
+class PyParametrizedArgumentInspection : PyInspection() {
+ override fun buildVisitor(holder: ProblemsHolder,
+ isOnTheFly: Boolean,
+ session: LocalInspectionToolSession) = object : PsiElementVisitor() {
+ override fun visitElement(element: PsiElement?) {
+ if (element is PyFunction) {
+ val requiredParameters = element.getParametersFromGenerator()
+ if (requiredParameters.isNotEmpty()) {
+ val declaredParameters = element.parameterList.parameters.mapNotNull(PyParameter::getName)
+ val diff = requiredParameters.minus(declaredParameters)
+ if (diff.isNotEmpty()) {
+ // Some params are not declared
+ holder.registerProblem(element, "Following arguments are not declared but provided by decorator: $diff",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
+ }
+ }
+ }
+ super.visitElement(element)
+ }
+ }
+
+ override fun getDisplayName(): String {
+ return PyBundle.message("INSP.NAME.pytest-parametrized")
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedInspectionExtension.kt b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedInspectionExtension.kt
new file mode 100644
index 000000000000..87e0b06db9ec
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParametrizedInspectionExtension.kt
@@ -0,0 +1,12 @@
+// 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.pyTestParametrized
+
+import com.intellij.psi.PsiElement
+import com.jetbrains.python.inspections.PyInspectionExtension
+import com.jetbrains.python.psi.PyNamedParameter
+
+class PyParametrizedInspectionExtension : PyInspectionExtension() {
+
+ override fun ignoreUnused(local: PsiElement) = local is PyNamedParameter && local.isParametrized()
+
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParamterized.kt b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParamterized.kt
new file mode 100644
index 000000000000..9f76848391e2
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestParametrized/PyParamterized.kt
@@ -0,0 +1,37 @@
+// 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.pyTestParametrized
+
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.ThreeState
+import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.PyNamedParameter
+import com.jetbrains.python.psi.impl.PyEvaluator
+import com.jetbrains.python.psi.types.TypeEvalContext
+import com.jetbrains.python.testing.isTestElement
+
+/**
+ * @return Boolean is parameter provided to function by parametrized decorator
+ */
+fun PyNamedParameter.isParametrized() = PsiTreeUtil.getParentOfType(this, PyFunction::class.java)
+ ?.getParametersFromGenerator()
+ ?.contains(name)
+ ?: false
+
+//TODO: Add inspection that checks all parameters are declared
+
+/**
+ * @return List if test function decorated with parametrize -- return parameter names
+ */
+internal fun PyFunction.getParametersFromGenerator(): List {
+ val decoratorList = decoratorList ?: return emptyList()
+ if (!isTestElement(this, ThreeState.NO, TypeEvalContext.codeAnalysis(project, containingFile))) {
+ return emptyList()
+ }
+ val pyEvaluator = PyEvaluator()
+ return decoratorList.decorators
+ .filter { it.name == "parametrize" }
+ .mapNotNull { pyEvaluator.evaluate(it.arguments.firstOrNull()) }
+ .filterIsInstance(String::class.java)
+ .flatMap { it.split(",") }
+ .map(String::trim)
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/pyTestParametrized/README.md b/python/src/com/jetbrains/python/testing/pyTestParametrized/README.md
new file mode 100644
index 000000000000..32362078b0ae
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/pyTestParametrized/README.md
@@ -0,0 +1,5 @@
+https://docs.pytest.org/en/latest/parametrize.html
+
+* Disable "unused" inspections for parameters
+* TODO: Support inspection that checks all params are declared
+* TODO: Support "rerun with certain parameters" (using "-k" option). Support from python side required
\ No newline at end of file