diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3d9f11..e6ef0a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,9 @@ jobs: pyright: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: cache: pip - name: Install typing dependencies @@ -30,14 +30,15 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Install dependencies run: python -m pip install --upgrade pip setuptools tox - name: Run tests with flake8 @@ -50,11 +51,11 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.13 - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - name: Set up Python 3.14 + uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox @@ -67,9 +68,9 @@ jobs: needs: [pyright, test] if: github.repository == 'python-trio/flake8-async' && github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v4 - - name: Set up Python 3 - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 - name: Install tools run: python -m pip install --upgrade build pip setuptools wheel twine gitpython - name: Upload new release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d9a8b6..10b7af6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,8 +29,9 @@ repos: rev: v3.21.0 hooks: - id: pyupgrade - args: [--py39-plus] - exclude: tests/eval_files/async103.py + args: [--py310-plus] + # async232 explicitly tests Optional[] + exclude: tests/eval_files/async(103|232|232_asyncio).py - repo: https://github.com/pycqa/isort rev: 7.0.0 @@ -41,8 +42,8 @@ repos: rev: v1.18.2 hooks: - id: mypy - # uses py311 syntax, mypy configured for py39 - exclude: tests/(eval|autofix)_files/.*_py(310|311).py + # uses py311 syntax, mypy configured for py310 + exclude: tests/(eval|autofix)_files/.*_py311.py - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.407 diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py index 89eb93d..5a2dbb8 100644 --- a/flake8_async/__init__.py +++ b/flake8_async/__init__.py @@ -151,7 +151,9 @@ def from_source( ) -> Plugin: plugin = Plugin.__new__(cls) super(Plugin, plugin).__init__() - plugin._tree = ast.parse(source) + plugin._tree = ast.parse( + source, filename=str(filename) if filename is not None else "" + ) plugin.filename = str(filename) if filename else None plugin.module = cst_parse_module_native(source) return plugin diff --git a/flake8_async/visitors/flake8asyncvisitor.py b/flake8_async/visitors/flake8asyncvisitor.py index 46b236c..8e4297a 100644 --- a/flake8_async/visitors/flake8asyncvisitor.py +++ b/flake8_async/visitors/flake8asyncvisitor.py @@ -4,7 +4,7 @@ import ast from abc import ABC -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import libcst as cst from libcst.metadata import PositionProvider @@ -16,7 +16,7 @@ from ..runner import SharedState - HasLineCol = Union[ast.expr, ast.stmt, ast.arg, ast.excepthandler, Statement] + HasLineCol = ast.expr | ast.stmt | ast.arg | ast.excepthandler | Statement class Flake8AsyncVisitor(ast.NodeVisitor, ABC): diff --git a/flake8_async/visitors/helpers.py b/flake8_async/visitors/helpers.py index ea2da3d..5764e16 100644 --- a/flake8_async/visitors/helpers.py +++ b/flake8_async/visitors/helpers.py @@ -9,7 +9,7 @@ from collections.abc import Sized from dataclasses import dataclass from fnmatch import fnmatch -from typing import TYPE_CHECKING, Generic, TypeVar, Union +from typing import TYPE_CHECKING, Generic, TypeVar import libcst as cst import libcst.matchers as m @@ -35,11 +35,9 @@ T = TypeVar("T", bound=Flake8AsyncVisitor) T_CST = TypeVar("T_CST", bound=Flake8AsyncVisitor_cst) - T_EITHER = TypeVar( - "T_EITHER", bound=Union[Flake8AsyncVisitor, Flake8AsyncVisitor_cst] - ) + T_EITHER = TypeVar("T_EITHER", bound=Flake8AsyncVisitor | Flake8AsyncVisitor_cst) -T_Call = TypeVar("T_Call", bound=Union[cst.Call, ast.Call]) +T_Call = TypeVar("T_Call", bound=cst.Call | ast.Call) def error_class(error_class: type[T]) -> type[T]: diff --git a/flake8_async/visitors/visitor91x.py b/flake8_async/visitors/visitor91x.py index 433c375..474bfe0 100644 --- a/flake8_async/visitors/visitor91x.py +++ b/flake8_async/visitors/visitor91x.py @@ -166,17 +166,12 @@ class LoopState: default_factory=set[Statement] ) uncheckpointed_before_break: set[Statement] = field(default_factory=set[Statement]) - # pyright emits reportUnknownVariableType, requiring the generic to default_factory - # to be specified. - # But for these we require a union, and `|` doesn't work on py39, and uses of - # `Union` gets autofixed by ruff. - # So.... let's just ignore the error for now - artificial_errors: set[ # pyright: ignore[reportUnknownVariableType] - cst.Return | cst.Yield - ] = field(default_factory=set) - nodes_needing_checkpoints: list[ # pyright: ignore[reportUnknownVariableType] - cst.Return | cst.Yield | ArtificialStatement - ] = field(default_factory=list) + artificial_errors: set[cst.Return | cst.Yield] = field( + default_factory=set[cst.Return | cst.Yield] + ) + nodes_needing_checkpoints: list[cst.Return | cst.Yield | ArtificialStatement] = ( + field(default_factory=list[cst.Return | cst.Yield | ArtificialStatement]) + ) def copy(self): return LoopState( diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py index e4fa1c8..4c83607 100644 --- a/flake8_async/visitors/visitors.py +++ b/flake8_async/visitors/visitors.py @@ -128,8 +128,7 @@ def visit_With(self, node: ast.With | ast.AsyncWith): nursery_type = "task group" start_methods = ("create_task",) else: - # incorrectly marked as not covered on py39 - continue # pragma: no cover # https://github.com/nedbat/coveragepy/issues/198 + continue body_call = node.body[0].value if isinstance(body_call, ast.Await): diff --git a/pyproject.toml b/pyproject.toml index ae0b618..8fa5e89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ skip_glob = "tests/*_files/*" [tool.mypy] check_untyped_defs = true disable_error_code = ["no-untyped-def", "misc", "no-untyped-call", "no-any-return"] -python_version = "3.9" +python_version = "3.10" strict = true warn_unreachable = true warn_unused_ignores = false @@ -84,7 +84,7 @@ extend-exclude = [ "tests/autofix_files/*" ] line-length = 90 -target-version = "py39" +target-version = "py310" [tool.ruff.lint] ignore = [ diff --git a/tests/autofix_files/async910.py b/tests/autofix_files/async910.py index 4d97ec3..0d67a69 100644 --- a/tests/autofix_files/async910.py +++ b/tests/autofix_files/async910.py @@ -430,23 +430,24 @@ async def try_bare_except_reraises(): ... -async def return_in_finally_bare_except_checkpoint(): - try: - await trio.sleep(0) - except: - await trio.sleep(0) - finally: - return - - -async def return_in_finally_bare_except_empty(): - try: - await trio.sleep(0) - except: - ... - finally: - await trio.lowlevel.checkpoint() - return # error: 8, 'return', Statement('function definition', lineno-6) +# return in finally is a SyntaxError on py314. We currently don't have +# test infra to set a max python version for an eval file. +# async def return_in_finally_bare_except_checkpoint(): +# try: +# await trio.sleep(0) +# except: +# await trio.sleep(0) +# finally: +# return +# +# +# async def return_in_finally_bare_except_empty(): +# try: +# await trio.sleep(0) +# except: +# ... +# finally: +# return # error: 8, 'return', Statement('function definition', lineno-6) # early return diff --git a/tests/autofix_files/async910.py.diff b/tests/autofix_files/async910.py.diff index 3fb6131..c765401 100644 --- a/tests/autofix_files/async910.py.diff +++ b/tests/autofix_files/async910.py.diff @@ -154,13 +154,7 @@ # safe -@@ x,16 x,19 @@ - except: - ... - finally: -+ await trio.lowlevel.checkpoint() - return # error: 8, 'return', Statement('function definition', lineno-6) - +@@ x,11 x,13 @@ # early return async def foo_return_1(): diff --git a/tests/autofix_files/async91x_autofix.py b/tests/autofix_files/async91x_autofix.py index 35fe6ff..746166a 100644 --- a/tests/autofix_files/async91x_autofix.py +++ b/tests/autofix_files/async91x_autofix.py @@ -144,3 +144,87 @@ async def no_checkpoint(): # ASYNC910: 0, "exit", Statement("function definitio except TypeError: ... await trio.lowlevel.checkpoint() + + +# structural pattern matching +async def match_subject() -> None: + match await foo(): + case False: + pass + + +async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match foo(): + case 1: + ... + case _: + await foo() + await trio.lowlevel.checkpoint() + + +async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match bar(): + case 1: + await foo() + case 2: + await foo() + case _ if True: + await foo() + await trio.lowlevel.checkpoint() + + +async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match bar(): + case 1: + await foo() + case 2: + await foo() + case _ if foo(): + await foo() + await trio.lowlevel.checkpoint() + + +async def match_all_cases() -> None: + match bar(): + case 1: + await foo() + case 2: + await foo() + case _: + await foo() + + +async def match_fallback_await_in_guard() -> None: + # The case guard is only executed if the pattern matches, so we can mostly treat + # it as part of the body, except for a special case for fallback+checkpointing guard. + match foo(): + case 1 if await foo(): + ... + case _ if await foo(): + ... + + +async def match_checkpoint_guard() -> None: + # The above pattern is quite cursed, but this seems fairly reasonable to do. + match foo(): + case 1 if await foo(): + ... + case _: + await foo() + + +async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match foo(): + case 1: + ... + case _ if await foo(): + ... + await trio.lowlevel.checkpoint() diff --git a/tests/autofix_files/async91x_autofix.py.diff b/tests/autofix_files/async91x_autofix.py.diff index e6e6625..30825a7 100644 --- a/tests/autofix_files/async91x_autofix.py.diff +++ b/tests/autofix_files/async91x_autofix.py.diff @@ -78,8 +78,40 @@ yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2) # ASYNC911: 8, "yield", Statement("yield", lineno) async def bar(): -@@ x,3 x,4 @@ +@@ x,6 x,7 @@ await foo("1") # type: ignore[call-arg] except TypeError: ... + await trio.lowlevel.checkpoint() + + + # structural pattern matching +@@ x,6 x,7 @@ + ... + case _: + await foo() ++ await trio.lowlevel.checkpoint() + + + async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) +@@ x,6 x,7 @@ + await foo() + case _ if True: + await foo() ++ await trio.lowlevel.checkpoint() + + + async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) +@@ x,6 x,7 @@ + await foo() + case _ if foo(): + await foo() ++ await trio.lowlevel.checkpoint() + + + async def match_all_cases() -> None: +@@ x,3 x,4 @@ + ... + case _ if await foo(): + ... ++ await trio.lowlevel.checkpoint() diff --git a/tests/autofix_files/async91x_py310.py b/tests/autofix_files/async91x_py310.py deleted file mode 100644 index 2f691d0..0000000 --- a/tests/autofix_files/async91x_py310.py +++ /dev/null @@ -1,90 +0,0 @@ -# ARG --enable=ASYNC910,ASYNC911,ASYNC913 -# AUTOFIX -# ASYNCIO_NO_AUTOFIX -import trio - - -async def foo(): ... - - -async def match_subject() -> None: - match await foo(): - case False: - pass - - -async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - ... - case _: - await foo() - await trio.lowlevel.checkpoint() - - -async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - await foo() - case 2: - await foo() - case _ if True: - await foo() - await trio.lowlevel.checkpoint() - - -async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - await foo() - case 2: - await foo() - case _ if foo(): - await foo() - await trio.lowlevel.checkpoint() - - -async def match_all_cases() -> None: - match foo(): - case 1: - await foo() - case 2: - await foo() - case _: - await foo() - - -async def match_fallback_await_in_guard() -> None: - # The case guard is only executed if the pattern matches, so we can mostly treat - # it as part of the body, except for a special case for fallback+checkpointing guard. - match foo(): - case 1 if await foo(): - ... - case _ if await foo(): - ... - - -async def match_checkpoint_guard() -> None: - # The above pattern is quite cursed, but this seems fairly reasonable to do. - match foo(): - case 1 if await foo(): - ... - case _: - await foo() - - -async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - ... - case _ if await foo(): - ... - await trio.lowlevel.checkpoint() diff --git a/tests/autofix_files/async91x_py310.py.diff b/tests/autofix_files/async91x_py310.py.diff deleted file mode 100644 index 47c84f3..0000000 --- a/tests/autofix_files/async91x_py310.py.diff +++ /dev/null @@ -1,31 +0,0 @@ ---- -+++ -@@ x,6 x,7 @@ - ... - case _: - await foo() -+ await trio.lowlevel.checkpoint() - - - async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) -@@ x,6 x,7 @@ - await foo() - case _ if True: - await foo() -+ await trio.lowlevel.checkpoint() - - - async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) -@@ x,6 x,7 @@ - await foo() - case _ if foo(): - await foo() -+ await trio.lowlevel.checkpoint() - - - async def match_all_cases() -> None: -@@ x,3 x,4 @@ - ... - case _ if await foo(): - ... -+ await trio.lowlevel.checkpoint() diff --git a/tests/eval_files/async103.py b/tests/eval_files/async103.py index 82bf8e1..a8bef0c 100644 --- a/tests/eval_files/async103.py +++ b/tests/eval_files/async103.py @@ -340,3 +340,39 @@ def foo() -> Any: ... ... except BaseException: # ASYNC103_trio: 11, "BaseException" ... + +# structural pattern matching +try: + ... +except BaseException as e: # ASYNC103_trio: 7, "BaseException" + match foo(): + case True: + raise e + case False: + ... + case _: + raise e + +try: + ... +except BaseException: # ASYNC103_trio: 7, "BaseException" + match foo(): + case True: + raise + +try: + ... +except BaseException: # safe + match foo(): + case True: + raise + case False: + raise + case _: + raise +try: + ... +except BaseException: # ASYNC103_trio: 7, "BaseException" + match foo(): + case _ if foo(): + raise diff --git a/tests/eval_files/async103_104_py310.py b/tests/eval_files/async103_104_py310.py deleted file mode 100644 index 965fe32..0000000 --- a/tests/eval_files/async103_104_py310.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Test for ASYNC103/ASYNC104 with structural pattern matching - -ASYNC103: no-reraise-cancelled -ASYNC104: cancelled-not-raised -""" - -# ARG --enable=ASYNC103,ASYNC104 - - -def foo() -> Any: ... - - -try: - ... -except BaseException as e: # ASYNC103_trio: 7, "BaseException" - match foo(): - case True: - raise e - case False: - ... - case _: - raise e - -try: - ... -except BaseException: # ASYNC103_trio: 7, "BaseException" - match foo(): - case True: - raise - -try: - ... -except BaseException: # safe - match foo(): - case True: - raise - case False: - raise - case _: - raise -try: - ... -except BaseException: # ASYNC103_trio: 7, "BaseException" - match foo(): - case _ if foo(): - raise -try: - ... -except BaseException: # ASYNC103_trio: 7, "BaseException" - match foo(): - case 1: - return # ASYNC104: 12 - case 2: - raise - case 3: - return # ASYNC104: 12 - case blah: - raise diff --git a/tests/eval_files/async104.py b/tests/eval_files/async104.py index 8b845c2..9d63f7a 100644 --- a/tests/eval_files/async104.py +++ b/tests/eval_files/async104.py @@ -104,7 +104,9 @@ def foo2(): else: return # type: ignore[unreachable] # error: 12 finally: - return # error: 12 + # a return here would also be an error - but it's a syntax error + # from py314+ and we don't have the test infra to handle that properly. + pass # don't avoid re-raise with continue/break @@ -183,3 +185,16 @@ def foo_cancelled_not_handled(): return # ASYNC104: 8 except: return # would otherwise error + +try: + ... +except BaseException: # ASYNC103_trio: 7, "BaseException" + match foo(): + case 1: + return # ASYNC104: 12 + case 2: + raise + case 3: + return # ASYNC104: 12 + case blah: + raise diff --git a/tests/eval_files/async232.py b/tests/eval_files/async232.py index 5fd52f4..b1b7f1f 100644 --- a/tests/eval_files/async232.py +++ b/tests/eval_files/async232.py @@ -227,12 +227,12 @@ async def attribute_access_on_object(): # The type checker is very naive, and will not do any parsing of logic pertaining # to the type -async def type_restricting_1(f: Optional[TextIOWrapper] = None): +async def type_restricting_1(f: TextIOWrapper | None = None): if f is None: f.read() # ASYNC232: 8, 'read', 'f', "trio" -async def type_restricting_2(f: Optional[TextIOWrapper] = None): +async def type_restricting_2(f: TextIOWrapper | None = None): if isinstance(f, TextIOWrapper): return f.read() # ASYNC232: 4, 'read', 'f', "trio" diff --git a/tests/eval_files/async232_asyncio.py b/tests/eval_files/async232_asyncio.py index ae070b7..5f40dd3 100644 --- a/tests/eval_files/async232_asyncio.py +++ b/tests/eval_files/async232_asyncio.py @@ -228,12 +228,12 @@ async def attribute_access_on_object(): # The type checker is very naive, and will not do any parsing of logic pertaining # to the type -async def type_restricting_1(f: Optional[TextIOWrapper] = None): +async def type_restricting_1(f: TextIOWrapper | None = None): if f is None: f.read() # ASYNC232_asyncio: 8, 'read', 'f' -async def type_restricting_2(f: Optional[TextIOWrapper] = None): +async def type_restricting_2(f: TextIOWrapper | None = None): if isinstance(f, TextIOWrapper): return f.read() # ASYNC232_asyncio: 4, 'read', 'f' diff --git a/tests/eval_files/async910.py b/tests/eval_files/async910.py index f8d7680..d370155 100644 --- a/tests/eval_files/async910.py +++ b/tests/eval_files/async910.py @@ -409,22 +409,24 @@ async def try_bare_except_reraises(): ... -async def return_in_finally_bare_except_checkpoint(): - try: - await trio.sleep(0) - except: - await trio.sleep(0) - finally: - return - - -async def return_in_finally_bare_except_empty(): - try: - await trio.sleep(0) - except: - ... - finally: - return # error: 8, 'return', Statement('function definition', lineno-6) +# return in finally is a SyntaxError on py314. We currently don't have +# test infra to set a max python version for an eval file. +# async def return_in_finally_bare_except_checkpoint(): +# try: +# await trio.sleep(0) +# except: +# await trio.sleep(0) +# finally: +# return +# +# +# async def return_in_finally_bare_except_empty(): +# try: +# await trio.sleep(0) +# except: +# ... +# finally: +# return # error: 8, 'return', Statement('function definition', lineno-6) # early return diff --git a/tests/eval_files/async91x_autofix.py b/tests/eval_files/async91x_autofix.py index 7ce0a35..f5e22d4 100644 --- a/tests/eval_files/async91x_autofix.py +++ b/tests/eval_files/async91x_autofix.py @@ -128,3 +128,83 @@ async def no_checkpoint(): # ASYNC910: 0, "exit", Statement("function definitio await foo("1") # type: ignore[call-arg] except TypeError: ... + + +# structural pattern matching +async def match_subject() -> None: + match await foo(): + case False: + pass + + +async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match foo(): + case 1: + ... + case _: + await foo() + + +async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match bar(): + case 1: + await foo() + case 2: + await foo() + case _ if True: + await foo() + + +async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match bar(): + case 1: + await foo() + case 2: + await foo() + case _ if foo(): + await foo() + + +async def match_all_cases() -> None: + match bar(): + case 1: + await foo() + case 2: + await foo() + case _: + await foo() + + +async def match_fallback_await_in_guard() -> None: + # The case guard is only executed if the pattern matches, so we can mostly treat + # it as part of the body, except for a special case for fallback+checkpointing guard. + match foo(): + case 1 if await foo(): + ... + case _ if await foo(): + ... + + +async def match_checkpoint_guard() -> None: + # The above pattern is quite cursed, but this seems fairly reasonable to do. + match foo(): + case 1 if await foo(): + ... + case _: + await foo() + + +async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) + None +): + match foo(): + case 1: + ... + case _ if await foo(): + ... diff --git a/tests/eval_files/async91x_py310.py b/tests/eval_files/async91x_py310.py deleted file mode 100644 index 9367fac..0000000 --- a/tests/eval_files/async91x_py310.py +++ /dev/null @@ -1,86 +0,0 @@ -# ARG --enable=ASYNC910,ASYNC911,ASYNC913 -# AUTOFIX -# ASYNCIO_NO_AUTOFIX -import trio - - -async def foo(): ... - - -async def match_subject() -> None: - match await foo(): - case False: - pass - - -async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - ... - case _: - await foo() - - -async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - await foo() - case 2: - await foo() - case _ if True: - await foo() - - -async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - await foo() - case 2: - await foo() - case _ if foo(): - await foo() - - -async def match_all_cases() -> None: - match foo(): - case 1: - await foo() - case 2: - await foo() - case _: - await foo() - - -async def match_fallback_await_in_guard() -> None: - # The case guard is only executed if the pattern matches, so we can mostly treat - # it as part of the body, except for a special case for fallback+checkpointing guard. - match foo(): - case 1 if await foo(): - ... - case _ if await foo(): - ... - - -async def match_checkpoint_guard() -> None: - # The above pattern is quite cursed, but this seems fairly reasonable to do. - match foo(): - case 1 if await foo(): - ... - case _: - await foo() - - -async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno) - None -): - match foo(): - case 1: - ... - case _ if await foo(): - ... diff --git a/tests/test_flake8_async.py b/tests/test_flake8_async.py index 771f59d..339fbdb 100644 --- a/tests/test_flake8_async.py +++ b/tests/test_flake8_async.py @@ -324,7 +324,7 @@ def test_eval( ): expected = [] - plugin = Plugin.from_source(content) + plugin = Plugin.from_source(content, filename=path) errors = assert_expected_errors( plugin, *expected, diff --git a/tox.ini b/tox.ini index dce3538..553d11d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ [tox] # default environments to run without `-e` # trailing comma gives the empty environ - i.e. no flake8 default python -envlist = py{39,310,311,312,313}-{flake8}, +envlist = py{310,311,312,313,314}-{flake8}, # create a default testenv, whose behaviour will depend on the name it's called with. # for CI you can call with `-e flake8_6,flake8_7` and let the CI handle python version