From 94c9f9b8182acc35bbb61fcdbf3cb6fde17cfd5d Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Mon, 28 May 2018 15:15:22 +0300 Subject: [PATCH] Add inspection for not awaited coroutines (PY-17292) --- .../PyAsyncCallInspection.html | 5 + python/src/META-INF/python-core-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 2 + .../inspections/PyAsyncCallInspection.kt | 104 ++++++++++++++++++ .../PyUnresolvedReferencesInspection.java | 7 +- .../python/psi/PyKnownDecoratorUtil.java | 10 ++ .../jetbrains/python/psi/types/PyABCUtil.java | 3 + .../AsyncioCorFromAsyncioCorCall/a.py | 15 +++ .../asyncio/__init__.py | 5 + .../asyncio/coroutines.py | 2 + .../CorrectAsyncioCorCall/a.py | 22 ++++ .../CorrectAsyncioCorCall/asyncio/__init__.py | 5 + .../asyncio/coroutines.py | 2 + .../TypesCorFromAsyncCall/a.py | 14 +++ .../TypesCorFromAsyncCall/types.py | 2 + .../asyncFromAsyncCall.py | 9 ++ .../PyAsyncCallInspection/correctCalls.py | 38 +++++++ .../futureLikeFromAsyncCall.py | 10 ++ .../addAwaitBeforeCall.py | 9 ++ .../addAwaitBeforeCall_after.py | 9 ++ .../addYieldFromBeforeCall.py | 13 +++ .../addYieldFromBeforeCall_after.py | 13 +++ .../asyncio/__init__.py | 5 + .../asyncio/coroutines.py | 2 + python/testData/quickFixes/__init__.py | 0 .../PyAsyncCallInspectionTest.java | 65 +++++++++++ .../quickFixes/PyAsyncCallQuickFixTest.java | 29 +++++ 27 files changed, 395 insertions(+), 6 deletions(-) create mode 100644 python/resources/inspectionDescriptions/PyAsyncCallInspection.html create mode 100644 python/src/com/jetbrains/python/inspections/PyAsyncCallInspection.kt create mode 100644 python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/a.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/__init__.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/coroutines.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/a.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/__init__.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/coroutines.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/a.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/types.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/asyncFromAsyncCall.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/correctCalls.py create mode 100644 python/testData/inspections/PyAsyncCallInspection/futureLikeFromAsyncCall.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall_after.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall_after.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/__init__.py create mode 100644 python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/coroutines.py create mode 100644 python/testData/quickFixes/__init__.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyAsyncCallInspectionTest.java create mode 100644 python/testSrc/com/jetbrains/python/quickFixes/PyAsyncCallQuickFixTest.java diff --git a/python/resources/inspectionDescriptions/PyAsyncCallInspection.html b/python/resources/inspectionDescriptions/PyAsyncCallInspection.html new file mode 100644 index 000000000000..9bc2d4281a40 --- /dev/null +++ b/python/resources/inspectionDescriptions/PyAsyncCallInspection.html @@ -0,0 +1,5 @@ + + +This inspection highlights coroutines which were called without await + + \ No newline at end of file diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 6694ab60ad24..9b3ad71c3301 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -430,6 +430,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 192a7e3b1c1d..7fbf6c32f5fb 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -603,6 +603,8 @@ 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 +#PyAsyncCallInspection +INSP.NAME.coroutine.is.not.awaited=Coroutine ''{0}'' is not awaited # PyTestParametrizedInspection INSP.NAME.pytest-parametrized=Checks that functions decorated by pytest parametrize have correct arguments diff --git a/python/src/com/jetbrains/python/inspections/PyAsyncCallInspection.kt b/python/src/com/jetbrains/python/inspections/PyAsyncCallInspection.kt new file mode 100644 index 000000000000..2e5e6a15b466 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyAsyncCallInspection.kt @@ -0,0 +1,104 @@ +// 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.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementVisitor +import com.jetbrains.python.PyBundle +import com.jetbrains.python.PyNames +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider +import com.jetbrains.python.psi.* +import com.jetbrains.python.psi.resolve.PyResolveContext +import com.jetbrains.python.psi.types.PyABCUtil +import com.jetbrains.python.psi.types.PyClassType +import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.python.refactoring.PyReplaceExpressionUtil + + +class PyAsyncCallInspection : PyInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return Visitor(holder, session) + } + + private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) { + + override fun visitPyExpressionStatement(node: PyExpressionStatement) { + val expr = node.expression + if (expr is PyCallExpression && isAwaitableCall(expr)) { + val awaitableType = when { + isOuterFunctionAsync(expr) -> AwaitableType.AWAITABLE + isOuterFunctionCoroutine(expr, myTypeEvalContext) -> AwaitableType.COROUTINE + else -> return + } + // no need to check whether await or yield from is missed, because it's PyCallExpression already, not PyPrefixExpression + val functionName = getCalledCoroutineName(expr, resolveContext) ?: return + registerProblem(node, PyBundle.message("INSP.NAME.coroutine.is.not.awaited", functionName), + PyAddAwaitCallForCoroutineFix(awaitableType)) + } + } + + private fun isAwaitableCall(callExpr: PyCallExpression): Boolean { + val type = myTypeEvalContext.getType(callExpr) ?: return false + return PyABCUtil.isSubtype(type, PyNames.AWAITABLE, myTypeEvalContext) || + type is PyClassType && + PyTypingTypeProvider.GENERATOR == type.classQName && + PyKnownDecoratorUtil.isResolvedToGeneratorBasedCoroutine(callExpr, resolveContext, myTypeEvalContext) + } + } + + companion object { + enum class AwaitableType { + AWAITABLE, COROUTINE + } + + const val coroutineIsNotAwaited = "Coroutine is not awaited" + + private fun getCalledCoroutineName(callExpression: PyCallExpression, resolveContext: PyResolveContext): String? { + val callee = callExpression.callee as? PyReferenceExpression ?: return null + val function = callExpression.multiResolveCalleeFunction(resolveContext).firstOrNull() as? PyFunction ?: return null + return if (function.name == PyNames.INIT) callee.name else function.name + } + + fun isOuterFunctionAsync(node: PyExpression): Boolean { + return (ScopeUtil.getScopeOwner(node) as? PyFunction)?.isAsync ?: false + } + + private fun isOuterFunctionCoroutine(node: PyExpression, typeEvalContext: TypeEvalContext): Boolean { + val pyFunction = (ScopeUtil.getScopeOwner(node) as? PyFunction) ?: return false + return PyKnownDecoratorUtil.hasGeneratorBasedCoroutineDecorator(pyFunction, typeEvalContext) + } + } + + private class PyAddAwaitCallForCoroutineFix(val type: AwaitableType) : LocalQuickFix { + override fun getFamilyName() = coroutineIsNotAwaited + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val psiElement = descriptor.psiElement + if (psiElement is PyExpressionStatement) { + val sb = StringBuilder() + when (type) { + AwaitableType.AWAITABLE -> { + sb.append("""async def foo(): + | """.trimMargin()).append(PyNames.AWAIT).append(" ").append(psiElement.text) + } + AwaitableType.COROUTINE -> { + sb.append("""def foo(): + | """.trimMargin()).append(PyNames.YIELD).append(" ").append(PyNames.FROM).append(" ") + .append(psiElement.text) + } + } + val generator = PyElementGenerator.getInstance(project) + val function = generator.createFromText(LanguageLevel.forElement(psiElement), PyFunction::class.java, sb.toString()) + val awaitedStatement = function.statementList.statements.firstOrNull() + if (awaitedStatement != null) { + PyReplaceExpressionUtil.replaceExpression(psiElement, awaitedStatement) + } + } + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index ac071e57b6e3..b5295251ca91 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -877,12 +877,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { final PyExpression receiver = ((PyOperatorReference)reference).getReceiver(); if (receiver instanceof PyCallExpression) { - final boolean resolvedToGeneratorBasedCoroutine = StreamEx - .of(((PyCallExpression)receiver).multiResolveCalleeFunction(getResolveContext())) - .select(PyFunction.class) - .anyMatch(function -> PyKnownDecoratorUtil.hasGeneratorBasedCoroutineDecorator(function, myTypeEvalContext)); - - if (resolvedToGeneratorBasedCoroutine) return true; + return PyKnownDecoratorUtil.isResolvedToGeneratorBasedCoroutine((PyCallExpression)receiver, getResolveContext(), myTypeEvalContext); } } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 18e3b81618c3..d152af33e4c4 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -6,6 +6,7 @@ import com.intellij.psi.PsiReference; import com.intellij.psi.util.QualifiedName; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.TypeEvalContext; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; @@ -210,6 +211,15 @@ public class PyKnownDecoratorUtil { return ContainerUtil.exists(getKnownDecorators(function, context), GENERATOR_BASED_COROUTINE_DECORATORS::contains); } + public static boolean isResolvedToGeneratorBasedCoroutine(@NotNull PyCallExpression receiver, + @NotNull PyResolveContext resolveContext, + @NotNull TypeEvalContext typeEvalContext) { + return StreamEx + .of((receiver).multiResolveCalleeFunction(resolveContext)) + .select(PyFunction.class) + .anyMatch(function -> hasGeneratorBasedCoroutineDecorator(function, typeEvalContext)); + } + public static boolean hasRedeclarationDecorator(@NotNull PyFunction function, @NotNull TypeEvalContext context) { return getKnownDecorators(function, context).contains(TYPING_OVERLOAD); } diff --git a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java index 483e30b6a4c5..10041326e459 100644 --- a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java +++ b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java @@ -97,6 +97,9 @@ public class PyABCUtil { if (PyNames.ASYNC_ITERABLE.equals(superClassName)) { return hasMethod(subClass, PyNames.AITER, inherited, context); } + if (PyNames.AWAITABLE.equals(superClassName)) { + return hasMethod(subClass, PyNames.DUNDER_AWAIT, inherited, context); + } if (PyNames.PATH_LIKE.equals(superClassName)) { return hasMethod(subClass, PyNames.FSPATH, inherited, context); } diff --git a/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/a.py b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/a.py new file mode 100644 index 000000000000..e2be236d4780 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/a.py @@ -0,0 +1,15 @@ +import asyncio + + +@asyncio.coroutine +def bar(): + yield from asyncio.sleep(1) + + +@asyncio.coroutine +def check(): + bar() + + +async def check(): + bar() \ No newline at end of file diff --git a/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/__init__.py b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/__init__.py new file mode 100644 index 000000000000..8e5f5dbc26be --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/__init__.py @@ -0,0 +1,5 @@ +from .coroutines import * + + +def sleep(s): + pass diff --git a/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/coroutines.py b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/coroutines.py new file mode 100644 index 000000000000..8e5abf01b538 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/AsyncioCorFromAsyncioCorCall/asyncio/coroutines.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass diff --git a/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/a.py b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/a.py new file mode 100644 index 000000000000..38d8023d0759 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/a.py @@ -0,0 +1,22 @@ +import asyncio + + +@asyncio.coroutine +def foo(): + return 24 + + +@asyncio.coroutine +def baz(): + yield from foo() + + +@asyncio.coroutine +def gen(): + return foo() + + +@asyncio.coroutine +def wrap(co): + res = yield from co + return res diff --git a/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/__init__.py b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/__init__.py new file mode 100644 index 000000000000..8e5f5dbc26be --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/__init__.py @@ -0,0 +1,5 @@ +from .coroutines import * + + +def sleep(s): + pass diff --git a/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/coroutines.py b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/coroutines.py new file mode 100644 index 000000000000..8e5abf01b538 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/CorrectAsyncioCorCall/asyncio/coroutines.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass diff --git a/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/a.py b/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/a.py new file mode 100644 index 000000000000..7fd44b685af6 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/a.py @@ -0,0 +1,14 @@ +import types + + +def foo(): + return 23 + + +@types.coroutine +def bar(): + yield from foo() + + +async def check(): + bar() \ No newline at end of file diff --git a/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/types.py b/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/types.py new file mode 100644 index 000000000000..68f0e4fbb99a --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/TypesCorFromAsyncCall/types.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyAsyncCallInspection/asyncFromAsyncCall.py b/python/testData/inspections/PyAsyncCallInspection/asyncFromAsyncCall.py new file mode 100644 index 000000000000..cf54c40f8776 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/asyncFromAsyncCall.py @@ -0,0 +1,9 @@ + + +async def bar(): + return "hey" + + +async def foo(): + bar() + return True diff --git a/python/testData/inspections/PyAsyncCallInspection/correctCalls.py b/python/testData/inspections/PyAsyncCallInspection/correctCalls.py new file mode 100644 index 000000000000..74f4b8e4d80f --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/correctCalls.py @@ -0,0 +1,38 @@ +import asyncio + + +async def foo(): + return 24 + + +async def with_await(): + await foo() + + +async def gen(): + return foo() + + +async def wrap(co): + await_co = await co + return await_co + + +async def baz(): + cor = foo() + return await wrap(cor) + + +async def wrap_twice(co): + await_co = await co + return await await_co + + +async def bar(): + return await wrap_twice(gen()) + + +loop = asyncio.get_event_loop() +loop.run_until_complete(bar()) +loop.run_until_complete(baz()) +loop.close() diff --git a/python/testData/inspections/PyAsyncCallInspection/futureLikeFromAsyncCall.py b/python/testData/inspections/PyAsyncCallInspection/futureLikeFromAsyncCall.py new file mode 100644 index 000000000000..5c329bfdf8a0 --- /dev/null +++ b/python/testData/inspections/PyAsyncCallInspection/futureLikeFromAsyncCall.py @@ -0,0 +1,10 @@ +import asyncio + + +class FutureLike: + def __await__(self): + yield from asyncio.sleep(2) + + +async def foo(): + FutureLike() \ No newline at end of file diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall.py new file mode 100644 index 000000000000..c29f3118dd79 --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall.py @@ -0,0 +1,9 @@ + + +async def bar(): + return "hey" + + +async def foo(): + bar() + return True diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall_after.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall_after.py new file mode 100644 index 000000000000..addd5e524d72 --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addAwaitBeforeCall_after.py @@ -0,0 +1,9 @@ + + +async def bar(): + return "hey" + + +async def foo(): + await bar() + return True diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall.py new file mode 100644 index 000000000000..7190bd54f4a7 --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall.py @@ -0,0 +1,13 @@ +import asyncio + + +@asyncio.coroutine +def bar(): + yield from asyncio.sleep(2) + return "hey" + + +@asyncio.coroutine +def foo(): + bar() + return True diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall_after.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall_after.py new file mode 100644 index 000000000000..289959537a5c --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/addYieldFromBeforeCall_after.py @@ -0,0 +1,13 @@ +import asyncio + + +@asyncio.coroutine +def bar(): + yield from asyncio.sleep(2) + return "hey" + + +@asyncio.coroutine +def foo(): + yield from bar() + return True diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/__init__.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/__init__.py new file mode 100644 index 000000000000..8e5f5dbc26be --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/__init__.py @@ -0,0 +1,5 @@ +from .coroutines import * + + +def sleep(s): + pass diff --git a/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/coroutines.py b/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/coroutines.py new file mode 100644 index 000000000000..8e5abf01b538 --- /dev/null +++ b/python/testData/quickFixes/PyAsyncCallQuickFixTest/asyncio/coroutines.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass diff --git a/python/testData/quickFixes/__init__.py b/python/testData/quickFixes/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAsyncCallInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAsyncCallInspectionTest.java new file mode 100644 index 000000000000..51555eb9de91 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyAsyncCallInspectionTest.java @@ -0,0 +1,65 @@ +// 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.inspections; + +import com.intellij.testFramework.LightProjectDescriptor; +import com.jetbrains.python.fixtures.PyInspectionTestCase; +import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +public class PyAsyncCallInspectionTest extends PyInspectionTestCase { + + // PY-17292 + public void testAsyncFromAsyncCall() { + doTest(); + } + + // PY-17292 + public void testCorrectAsyncioCorCall() { + doMultiFileTest("a.py"); + } + + // PY-17292 + public void testTypesCorFromAsyncCall() { + doMultiFileTest("a.py"); + } + + // PY-17292 + public void testAsyncioCorFromAsyncioCorCall() { + doMultiFileTest("a.py"); + } + + // PY-17292 + public void testFutureLikeFromAsyncCall() { + doTest(); + } + + // PY-17292 + public void testCorrectCalls() { + doTest(); + } + + + @Override + protected void doMultiFileTest(@NotNull String filename) { + runWithLanguageLevel(LanguageLevel.PYTHON35, () -> super.doMultiFileTest(filename)); + } + + @Override + protected void doTest() { + runWithLanguageLevel(LanguageLevel.PYTHON35, () -> super.doTest()); + } + + @NotNull + @Override + protected Class getInspectionClass() { + return PyAsyncCallInspection.class; + } + + @Nullable + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return ourPy3Descriptor; + } +} diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyAsyncCallQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyAsyncCallQuickFixTest.java new file mode 100644 index 000000000000..7068760130c4 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyAsyncCallQuickFixTest.java @@ -0,0 +1,29 @@ +// 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.quickFixes; + +import com.intellij.testFramework.LightProjectDescriptor; +import com.jetbrains.python.PyQuickFixTestCase; +import com.jetbrains.python.inspections.PyAsyncCallInspection; +import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.Nullable; + +public class PyAsyncCallQuickFixTest extends PyQuickFixTestCase { + + // PY-17292 + public void testAddAwaitBeforeCall() { + doQuickFixTest(PyAsyncCallInspection.class, PyAsyncCallInspection.coroutineIsNotAwaited, LanguageLevel.PYTHON35); + } + + // PY-17292 + public void testAddYieldFromBeforeCall() { + runWithLanguageLevel(LanguageLevel.PYTHON35, + () -> doMultifilesTest(PyAsyncCallInspection.class, PyAsyncCallInspection.coroutineIsNotAwaited, + new String[]{"addYieldFromBeforeCall.py", "asyncio/__init__.py", "asyncio/coroutines.py"})); + } + + @Nullable + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return ourPy3Descriptor; + } +}