From c18c7014db175aaa1cccd8d6048426c37dedeb01 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Tue, 13 Feb 2024 18:23:10 +0100 Subject: [PATCH 01/10] update pre-commit, fix pyright/mypy/ruff errors, remove flake8, add py312 testing, fix TRIO91X for empty-body-on-same-line found due to new black style --- .github/workflows/ci.yml | 26 ++++---- .pre-commit-config.yaml | 45 ++++--------- flake8_trio/__main__.py | 1 + flake8_trio/base.py | 12 ++-- flake8_trio/visitors/flake8triovisitor.py | 6 +- flake8_trio/visitors/visitor100.py | 3 +- flake8_trio/visitors/visitor101.py | 1 + flake8_trio/visitors/visitor103_104.py | 1 - flake8_trio/visitors/visitor118.py | 1 - flake8_trio/visitors/visitor91x.py | 21 ++++-- pyproject.toml | 19 ++++-- tests/autofix_files/trio910.py | 18 +++--- tests/autofix_files/trio911.py | 72 +++++++++++---------- tests/autofix_files/trio911.py.diff | 16 ++--- tests/autofix_files/trio91x_autofix.py | 22 +++---- tests/autofix_files/trio91x_autofix.py.diff | 10 +-- tests/eval_files/noqa_no_autofix.py | 3 +- tests/eval_files/trio102.py | 4 +- tests/eval_files/trio102_anyio.py | 3 +- tests/eval_files/trio102_trio.py | 3 +- tests/eval_files/trio103.py | 16 ++--- tests/eval_files/trio103_no_104.py | 3 +- tests/eval_files/trio103_trio.py | 3 +- tests/eval_files/trio105.py | 3 +- tests/eval_files/trio109.py | 36 ++++------- tests/eval_files/trio110.py | 3 +- tests/eval_files/trio113_trio.py | 3 +- tests/eval_files/trio114.py | 12 ++-- tests/eval_files/trio117.py | 3 +- tests/eval_files/trio910.py | 18 +++--- tests/eval_files/trio911.py | 72 +++++++++++---------- tests/eval_files/trio91x_autofix.py | 22 +++---- tests/test_config_and_args.py | 17 +++-- tests/test_flake8_trio.py | 14 ++-- tox.ini | 18 +----- typings/flake8/main/application.pyi | 13 +++- typings/flake8/options/manager.pyi | 8 +++ 37 files changed, 264 insertions(+), 287 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 933719a..924f919 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,11 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.11 - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pre-commit @@ -27,12 +27,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -47,11 +47,11 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.11 - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox @@ -64,9 +64,9 @@ jobs: needs: [check, test] if: github.repository == 'Zac-HD/flake8-trio' && github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3 - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 - 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 55e8de1..598ce1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,34 +8,34 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 24.2.0 hooks: - id: black args: [--preview] - repo: https://github.com/PyCQA/autoflake - rev: v2.0.2 + rev: v2.2.1 hooks: - id: autoflake - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.8.0 hooks: - id: mypy - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.299 + rev: v1.1.350 hooks: - id: pyright # ignore warnings about new version being available, no other warnings @@ -53,14 +53,14 @@ repos: - trio - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.6 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-merge-conflict - id: check-toml @@ -74,40 +74,17 @@ repos: args: ['--markdown-linebreak-ext=md,markdown'] - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.269 + rev: v0.2.1 hooks: - id: ruff - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - # this doesn't seem to work, pyi files don't get checked with --all-files - types_or: [python, pyi] - language_version: python3 - additional_dependencies: - - flake8-2020 - - flake8-bugbear - - flake8-builtins - - flake8-comprehensions - - flake8-datetimez - - flake8-docstrings - - flake8-mutable # not official supported by ruff - - flake8-pie - - flake8-pyi - - flake8-pytest-style - - flake8-return - - flake8-simplify - - flake8-type-checking - # all other are - - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt - rev: 0.2.2 + rev: 0.2.3 hooks: - id: yamlfmt - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.8.0 + rev: v2.12.0 hooks: - id: pretty-format-toml args: [--autofix] diff --git a/flake8_trio/__main__.py b/flake8_trio/__main__.py index 4f399f5..01e07dc 100644 --- a/flake8_trio/__main__.py +++ b/flake8_trio/__main__.py @@ -1,4 +1,5 @@ """Entry file when executed with `python -m`.""" + import sys from . import main diff --git a/flake8_trio/base.py b/flake8_trio/base.py index 8b05507..853afc7 100644 --- a/flake8_trio/base.py +++ b/flake8_trio/base.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, NamedTuple +from typing import TYPE_CHECKING, NamedTuple if TYPE_CHECKING: from collections.abc import Collection @@ -29,10 +29,12 @@ class Statement(NamedTuple): lineno: int col_offset: int = -1 - def __eq__(self, other: Any) -> bool: + # pyright is unhappy about defining __eq__ but not __hash__ .. which it should + # but it works :tm: and needs changing in a couple places to avoid it. + def __eq__(self, other: object) -> bool: return ( isinstance(other, Statement) - and self[:2] == other[:2] # type: ignore + and self[:2] == other[:2] and ( self.col_offset == other.col_offset or -1 in (self.col_offset, other.col_offset) @@ -68,11 +70,11 @@ def cmp(self): return self.line, self.code, self.args, self.col # for sorting in tests - def __lt__(self, other: Any) -> bool: + def __lt__(self, other: Error) -> bool: assert isinstance(other, Error) return self.cmp() < other.cmp() - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: return isinstance(other, Error) and self.cmp() == other.cmp() def __repr__(self) -> str: # pragma: no cover diff --git a/flake8_trio/visitors/flake8triovisitor.py b/flake8_trio/visitors/flake8triovisitor.py index 14eb78e..4c4bf48 100644 --- a/flake8_trio/visitors/flake8triovisitor.py +++ b/flake8_trio/visitors/flake8triovisitor.py @@ -4,7 +4,7 @@ import ast from abc import ABC -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any, ClassVar, Union import libcst as cst from libcst.metadata import PositionProvider @@ -23,7 +23,9 @@ class Flake8TrioVisitor(ast.NodeVisitor, ABC): # abstract attribute by not providing a value - error_codes: dict[str, str] # pyright: ignore[reportUninitializedInstanceVariable] + error_codes: ClassVar[ + dict[str, str] + ] # pyright: ignore[reportUninitializedInstanceVariable] def __init__(self, shared_state: SharedState): super().__init__() diff --git a/flake8_trio/visitors/visitor100.py b/flake8_trio/visitors/visitor100.py index db698ab..157c541 100644 --- a/flake8_trio/visitors/visitor100.py +++ b/flake8_trio/visitors/visitor100.py @@ -5,11 +5,12 @@ the timeout can only be triggered by a checkpoint. Checkpoints on Await, Async For and Async With """ + from __future__ import annotations from typing import Any -import libcst as cst # noqa: TCH002 +import libcst as cst import libcst.matchers as m from .flake8triovisitor import Flake8TrioVisitor_cst diff --git a/flake8_trio/visitors/visitor101.py b/flake8_trio/visitors/visitor101.py index 53a819d..ae2e873 100644 --- a/flake8_trio/visitors/visitor101.py +++ b/flake8_trio/visitors/visitor101.py @@ -3,6 +3,7 @@ `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling. """ + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/flake8_trio/visitors/visitor103_104.py b/flake8_trio/visitors/visitor103_104.py index 7963627..ee2c7ca 100644 --- a/flake8_trio/visitors/visitor103_104.py +++ b/flake8_trio/visitors/visitor103_104.py @@ -6,7 +6,6 @@ an improper raise, or other flow control, is encountered. """ - from __future__ import annotations import ast diff --git a/flake8_trio/visitors/visitor118.py b/flake8_trio/visitors/visitor118.py index 9a5bd85..6fff6a0 100644 --- a/flake8_trio/visitors/visitor118.py +++ b/flake8_trio/visitors/visitor118.py @@ -4,7 +4,6 @@ that breaks linter checks and multi-backend programs. """ - from __future__ import annotations import ast diff --git a/flake8_trio/visitors/visitor91x.py b/flake8_trio/visitors/visitor91x.py index 15e1275..6390186 100644 --- a/flake8_trio/visitors/visitor91x.py +++ b/flake8_trio/visitors/visitor91x.py @@ -35,10 +35,16 @@ def func_empty_body(node: cst.FunctionDef) -> bool: """Check if function body consist of `pass`, `...`, and/or (doc)string literals.""" empty_statement = m.Pass() | m.Expr(m.Ellipsis() | m.SimpleString()) + return m.matches( node.body, - m.IndentedBlock( - [m.ZeroOrMore(m.SimpleStatementLine([m.ZeroOrMore(empty_statement)]))] + m.OneOf( + # newline + indented statements + m.IndentedBlock( + [m.ZeroOrMore(m.SimpleStatementLine([m.ZeroOrMore(empty_statement)]))] + ), + # same-line statement[s] + m.SimpleStatementSuite(body=[m.ZeroOrMore(empty_statement)]), ), ) @@ -114,12 +120,10 @@ def __init__(self): @property @abstractmethod - def library(self) -> tuple[str, ...]: - ... + def library(self) -> tuple[str, ...]: ... @abstractmethod - def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool: - ... + def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool: ... # instead of trying to exclude yields found in all the weird places from # setting self.add_statement, we instead clear it upon each new line. @@ -587,7 +591,10 @@ def visit_While_body(self, node: cst.For | cst.While): if getattr(node, "asynchronous", None): self.uncheckpointed_statements = set() else: - self.uncheckpointed_statements = {ARTIFICIAL_STATEMENT} + # pyright correctly dislikes Statement defining __eq__ but not __hash__ + # but it works:tm:, and changing it touches on various bits of code, so + # leaving it for another time. + self.uncheckpointed_statements = {ARTIFICIAL_STATEMENT} # pyright: ignore self.loop_state.uncheckpointed_before_continue = set() self.loop_state.uncheckpointed_before_break = set() diff --git a/pyproject.toml b/pyproject.toml index 3190c8f..579b6ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,10 @@ extend-exclude = [ "tests/eval_files/*", "tests/autofix_files/*" ] +line-length = 90 +target-version = "py39" + +[tool.ruff.lint] ignore = [ "COM", # flake8-comma, handled by black "ANN", # annotations, handled by pyright/mypy @@ -58,7 +62,7 @@ ignore = [ "D213", # multi-line-summary-second-line "EM101", # exception must not use a string literal "EM102", # exception must not use an f-string literal - 'PGH001', # No builtin `eval()` allowed + 'S307', # No builtin `eval()` allowed 'N802', # function name should be lowercase - not an option with inheritance 'PTH123', # `open()` should be replaced by `Path.open()` 'PYI021', # docstring in stub @@ -86,16 +90,17 @@ ignore = [ 'TD002', # missing author in TODO 'TD003', # missing issue link in TODO 'TRY003', # Avoid specifying long messages outside the exception class - 'TRY200', # Use `raise from` to specify exception cause - 'TRY201' # Use `raise` without specifying exception name + 'B904', # Use `raise from` to specify exception cause + 'TRY201', # Use `raise` without specifying exception name + 'FIX002', # line contains #TODO + 'RUF012' # Mutable class attribute should be annotated with `typing.ClassVar` ] -line-length = 90 +# RUF012 occurs in 25 places ... I'm not going to fix that unless type checkers also complain select = ["ALL"] -target-version = "py39" -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # docstrings, and arguments we can't modify -"*.pyi" = ["D", 'FBT001', 'PLR0913'] +"*.pyi" = ["D", 'FBT001', 'PLR0913', "PIE790", "PYI048"] # imports "flake8_trio/visitors/__init__.py" = [ "F401", diff --git a/tests/autofix_files/trio910.py b/tests/autofix_files/trio910.py index fe301a1..bd752dd 100644 --- a/tests/autofix_files/trio910.py +++ b/tests/autofix_files/trio910.py @@ -15,8 +15,7 @@ async def foo() -> Any: await foo() -def bar() -> Any: - ... +def bar() -> Any: ... # ARG --enable=TRIO910,TRIO911 @@ -24,8 +23,12 @@ def bar() -> Any: # function whose body solely consists of pass, ellipsis, or string constants is safe -async def foo_empty_1(): +# fmt: off +async def foo_empty_1a(): ... +async def foo_empty_1b(): ... +async def foo_empty_1c(): ...; ... +# fmt: on async def foo_empty_2(): @@ -124,19 +127,16 @@ def foo_normal_func_1(): return -def foo_normal_func_2(): - ... +def foo_normal_func_2(): ... # overload decorator @overload -async def foo_overload_1(_: bytes): - ... +async def foo_overload_1(_: bytes): ... @typing.overload -async def foo_overload_1(_: str): - ... +async def foo_overload_1(_: str): ... async def foo_overload_1(_: bytes | str): diff --git a/tests/autofix_files/trio911.py b/tests/autofix_files/trio911.py index 501018d..3cfcca1 100644 --- a/tests/autofix_files/trio911.py +++ b/tests/autofix_files/trio911.py @@ -13,8 +13,11 @@ async def foo() -> Any: await foo() -def bar(*args) -> Any: - ... +def bar(*args) -> Any: ... + + +# mypy now treats `if ...` as `if True`, so we have another arbitrary function instead +def condition() -> Any: ... async def foo_yield_1(): @@ -94,7 +97,7 @@ async def foo_async_for(): # error: 0, "exit", Statement("yield", lineno+4) async def foo_async_for_2(): # error: 0, "exit", Statement("yield", lineno+2) async for i in trio.trick_pyright: yield - if ...: + if condition(): break await trio.lowlevel.checkpoint() @@ -211,7 +214,7 @@ async def foo_while_continue_1(): # error: 0, "exit", Statement("yield", lineno while foo(): await trio.lowlevel.checkpoint() yield # error: 8, "yield", Statement("yield", lineno) - if ...: + if condition(): continue await foo() await trio.lowlevel.checkpoint() @@ -226,7 +229,7 @@ async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno if foo(): continue await foo() - if ...: + if condition(): continue while foo(): yield # safe @@ -238,7 +241,7 @@ async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno # else might not run async def foo_while_break_1(): # error: 0, "exit", Statement("yield", lineno+6) while foo(): - if ...: + if condition(): break else: await foo() @@ -252,7 +255,7 @@ async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3) await foo() while foo(): yield # safe - if ...: + if condition(): break await foo() await trio.lowlevel.checkpoint() @@ -262,7 +265,7 @@ async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3) async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7) while foo(): await foo() - if ...: + if condition(): break # if it breaks, have checkpointed else: await foo() # runs if 0-iter @@ -273,7 +276,7 @@ async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7) # break at non-guaranteed checkpoint async def foo_while_break_4(): # error: 0, "exit", Statement("yield", lineno+7) while foo(): - if ...: + if condition(): break await foo() # might not run else: @@ -288,7 +291,7 @@ async def foo_while_break_5(): # error: 0, "exit", Statement("yield", lineno+12 await foo() while foo(): yield - if ...: + if condition(): break await foo() while foo(): @@ -306,12 +309,12 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11 await foo() while foo(): yield - if ...: + if condition(): break await foo() yield await foo() - if ...: + if condition(): break await trio.lowlevel.checkpoint() yield # error: 4, "yield", Statement("yield", lineno-8) @@ -321,7 +324,7 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11 async def foo_while_break_7(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+5) while foo(): await foo() - if ...: + if condition(): break yield break @@ -517,7 +520,7 @@ async def foo_try_10_no_except(): # if async def foo_if_1(): - if ...: + if condition(): await trio.lowlevel.checkpoint() yield # error: 8, "yield", Statement("function definition", lineno-2) await foo() @@ -529,7 +532,7 @@ async def foo_if_1(): async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6) await foo() - if ...: + if condition(): ... else: yield @@ -540,7 +543,7 @@ async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6) async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6) await foo() - if ...: + if condition(): yield else: ... @@ -552,7 +555,7 @@ async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6) async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7) await foo() yield - if ...: + if condition(): await foo() else: ... @@ -563,7 +566,7 @@ async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7) async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8) await foo() - if ...: + if condition(): yield await foo() else: @@ -576,7 +579,7 @@ async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8) async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8) await foo() - if ...: + if condition(): yield else: yield @@ -588,7 +591,7 @@ async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8) async def foo_if_7(): # error: 0, "exit", Statement("function definition", lineno) - if ...: + if condition(): await foo() yield await foo() @@ -596,7 +599,7 @@ async def foo_if_7(): # error: 0, "exit", Statement("function definition", line async def foo_if_8(): # error: 0, "exit", Statement("function definition", lineno) - if ...: + if condition(): ... else: await foo() @@ -617,7 +620,7 @@ async def foo_ifexp_2(): # error: 0, "exit", Statement("yield", lineno+2) await trio.lowlevel.checkpoint() print( (yield) # error: 9, "yield", Statement("function definition", lineno-2) - if ... and await foo() + if condition() and await foo() else await foo() ) await trio.lowlevel.checkpoint() @@ -628,8 +631,7 @@ def foo_sync_1(): return -def foo_sync_2(): - ... +def foo_sync_2(): ... def foo_sync_3(): @@ -637,13 +639,13 @@ def foo_sync_3(): def foo_sync_4(): - if ...: + if condition(): return yield def foo_sync_5(): - if ...: + if condition(): return yield @@ -655,7 +657,7 @@ def foo_sync_6(): def foo_sync_7(): while foo(): - if ...: + if condition(): return yield @@ -714,7 +716,7 @@ async def foo_loop_static(): for _ in [1, 2, 3]: await foo() - if ...: + if condition(): break else: yield @@ -723,7 +725,7 @@ async def foo_loop_static(): # continue for _ in [1, 2, 3]: - if ...: + if condition(): continue await foo() await trio.lowlevel.checkpoint() @@ -731,7 +733,7 @@ async def foo_loop_static(): # continue/else for _ in [1, 2, 3]: - if ...: + if condition(): continue await foo() else: @@ -742,7 +744,7 @@ async def foo_loop_static(): for _ in [1, 2, 3]: await foo() - if ...: + if condition(): break else: yield @@ -876,22 +878,22 @@ async def foo_loop_static(): # while while True: await foo() - if ...: + if condition(): break yield while True: - if ...: + if condition(): break await foo() await trio.lowlevel.checkpoint() yield # error: 4, "yield", Stmt("yield", line-6) while True: - if ...: + if condition(): continue await foo() - if ...: + if condition(): break yield diff --git a/tests/autofix_files/trio911.py.diff b/tests/autofix_files/trio911.py.diff index 91e9bb7..bf853b9 100644 --- a/tests/autofix_files/trio911.py.diff +++ b/tests/autofix_files/trio911.py.diff @@ -58,7 +58,7 @@ # await anext(iter) is not called on break @@ x,6 x,7 @@ yield - if ...: + if condition(): break + await trio.lowlevel.checkpoint() @@ -157,7 +157,7 @@ while foo(): + await trio.lowlevel.checkpoint() yield # error: 8, "yield", Statement("yield", lineno) - if ...: + if condition(): continue await foo() + await trio.lowlevel.checkpoint() @@ -190,7 +190,7 @@ # no checkpoint on break @@ x,6 x,7 @@ - if ...: + if condition(): break await foo() + await trio.lowlevel.checkpoint() @@ -227,7 +227,7 @@ # check multiple breaks @@ x,7 x,9 @@ await foo() - if ...: + if condition(): break + await trio.lowlevel.checkpoint() yield # error: 4, "yield", Statement("yield", lineno-8) @@ -364,7 +364,7 @@ @@ x,9 x,11 @@ # if async def foo_if_1(): - if ...: + if condition(): + await trio.lowlevel.checkpoint() yield # error: 8, "yield", Statement("function definition", lineno-2) await foo() @@ -450,7 +450,7 @@ + await trio.lowlevel.checkpoint() print( (yield) # error: 9, "yield", Statement("function definition", lineno-2) - if ... and await foo() + if condition() and await foo() else await foo() ) + await trio.lowlevel.checkpoint() @@ -491,7 +491,7 @@ # loop over non-empty static collection @@ x,6 x,7 @@ - if ...: + if condition(): continue await foo() + await trio.lowlevel.checkpoint() @@ -585,7 +585,7 @@ # while @@ x,6 x,7 @@ - if ...: + if condition(): break await foo() + await trio.lowlevel.checkpoint() diff --git a/tests/autofix_files/trio91x_autofix.py b/tests/autofix_files/trio91x_autofix.py index 874b6fe..685608c 100644 --- a/tests/autofix_files/trio91x_autofix.py +++ b/tests/autofix_files/trio91x_autofix.py @@ -12,8 +12,7 @@ import trio -def bar() -> Any: - ... +def bar() -> Any: ... async def foo() -> Any: @@ -39,10 +38,10 @@ async def foo_yield(): # TRIO911: 0, "exit", Statement("yield", lineno+2) async def foo_if(): - if ...: + if foo(): await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif ...: + elif foo(): await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: @@ -67,7 +66,7 @@ async def foo_while2(): async def foo_while3(): await foo() while True: - if ...: + if foo(): return await foo() @@ -75,10 +74,10 @@ async def foo_while3(): # check that multiple checkpoints don't get inserted async def foo_while4(): while True: - if ...: + if foo(): await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if ...: + if foo(): await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways @@ -103,7 +102,7 @@ async def foo_while_nested_func(): yield # TRIO911: 8, "yield", Statement("function definition", lineno-2) # TRIO911: 8, "yield", Statement("yield", lineno) async def bar(): - while ...: + while foo(): ... await foo() @@ -111,17 +110,16 @@ async def bar(): # Code coverage: visitors run when inside a sync function that has an async function. # When sync funcs don't contain an async func the body is not visited. def sync_func(): - async def async_func(): - ... + async def async_func(): ... try: ... except: ... - if ... and ...: + if foo() and foo(): ... while ...: - if ...: + if foo(): continue break [... for i in range(5)] diff --git a/tests/autofix_files/trio91x_autofix.py.diff b/tests/autofix_files/trio91x_autofix.py.diff index 34a1cf9..08a2ea8 100644 --- a/tests/autofix_files/trio91x_autofix.py.diff +++ b/tests/autofix_files/trio91x_autofix.py.diff @@ -7,7 +7,7 @@ +import trio - def bar() -> Any: + def bar() -> Any: ... @@ x,30 x,38 @@ async def foo1(): # TRIO910: 0, "exit", Statement("function definition", lineno) @@ -29,10 +29,10 @@ async def foo_if(): - if ...: + if foo(): + await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif ...: + elif foo(): + await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: @@ -50,10 +50,10 @@ @@ x,8 x,10 @@ async def foo_while4(): while True: - if ...: + if foo(): + await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if ...: + if foo(): + await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways diff --git a/tests/eval_files/noqa_no_autofix.py b/tests/eval_files/noqa_no_autofix.py index 8dcc56b..c179b75 100644 --- a/tests/eval_files/noqa_no_autofix.py +++ b/tests/eval_files/noqa_no_autofix.py @@ -5,8 +5,7 @@ # errors from AST visitors -async def foo() -> Any: - ... +async def foo() -> Any: ... async def foo_no_noqa_102(): diff --git a/tests/eval_files/trio102.py b/tests/eval_files/trio102.py index 5560e9a..2d501f1 100644 --- a/tests/eval_files/trio102.py +++ b/tests/eval_files/trio102.py @@ -120,7 +120,9 @@ async def foo(): try: pass finally: - async for i in trio.bypasslinters: # error: 8, Statement("try/finally", lineno-3) + async for ( # error: 8, Statement("try/finally", lineno-3) + i + ) in trio.bypasslinters: pass try: pass diff --git a/tests/eval_files/trio102_anyio.py b/tests/eval_files/trio102_anyio.py index 29f42c5..ec71f48 100644 --- a/tests/eval_files/trio102_anyio.py +++ b/tests/eval_files/trio102_anyio.py @@ -5,8 +5,7 @@ # this one is fine to also run with TRIO -async def foo(): - ... +async def foo(): ... async def foo_anyio(): diff --git a/tests/eval_files/trio102_trio.py b/tests/eval_files/trio102_trio.py index 20c8abb..58d58a5 100644 --- a/tests/eval_files/trio102_trio.py +++ b/tests/eval_files/trio102_trio.py @@ -2,8 +2,7 @@ import trio -async def foo(): - ... +async def foo(): ... # except cancelled/baseexception are also critical diff --git a/tests/eval_files/trio103.py b/tests/eval_files/trio103.py index bbf3e42..52829fc 100644 --- a/tests/eval_files/trio103.py +++ b/tests/eval_files/trio103.py @@ -3,8 +3,7 @@ from typing import Any -def foo() -> Any: - ... +def foo() -> Any: ... # fmt: off @@ -48,9 +47,9 @@ def foo() -> Any: try: ... except BaseException as e: # TRIO103_trio: 7, "BaseException" - if ...: + if foo(): raise e - elif ...: + elif foo(): ... else: raise e @@ -64,9 +63,9 @@ def foo() -> Any: try: ... except BaseException: # safe - if ...: + if foo(): raise - elif ...: + elif foo(): raise else: raise @@ -199,9 +198,8 @@ def foo() -> Any: except ( my_super_mega_long_exception_so_it_gets_split, SyntaxError, - BaseException, # TRIO103_trio: 4, "BaseException" - ValueError, - BaseException, # no complaint on this line + BaseException, # TRIO103_trio: 4, "BaseException" + ValueError, # no complaint on this line ): ... diff --git a/tests/eval_files/trio103_no_104.py b/tests/eval_files/trio103_no_104.py index 91c4646..63c37b1 100644 --- a/tests/eval_files/trio103_no_104.py +++ b/tests/eval_files/trio103_no_104.py @@ -3,8 +3,7 @@ from typing import Any -def foo() -> Any: - ... +def foo() -> Any: ... # nested try diff --git a/tests/eval_files/trio103_trio.py b/tests/eval_files/trio103_trio.py index 37a17d1..870acc9 100644 --- a/tests/eval_files/trio103_trio.py +++ b/tests/eval_files/trio103_trio.py @@ -6,8 +6,7 @@ import trio -def foo() -> Any: - ... +def foo() -> Any: ... # fmt: off diff --git a/tests/eval_files/trio105.py b/tests/eval_files/trio105.py index 067d6cc..8e18cae 100644 --- a/tests/eval_files/trio105.py +++ b/tests/eval_files/trio105.py @@ -8,8 +8,7 @@ async_funpar: Coroutine[Any, Any, Any] = ... # type: ignore -async def myasyncfun(task_status): - ... +async def myasyncfun(task_status): ... # calls that don't return diff --git a/tests/eval_files/trio109.py b/tests/eval_files/trio109.py index b652c10..139e2e5 100644 --- a/tests/eval_files/trio109.py +++ b/tests/eval_files/trio109.py @@ -5,8 +5,7 @@ timeout = 10 -async def foo(): - ... +async def foo(): ... # args @@ -36,8 +35,7 @@ async def foo_5( my_timeout, timeout_, timeout, # error: 4, "trio" -): - ... +): ... # posonlyargs @@ -45,54 +43,44 @@ async def foo_6( timeout, # error: 4, "trio" /, bar, -): - ... +): ... # kwonlyargs async def foo_7( *, timeout, # error: 4, "trio" -): - ... +): ... # kwonlyargs (and kw_defaults) async def foo_8( *, timeout=5, # error: 4, "trio" -): - ... +): ... -async def foo_9(k=timeout): - ... +async def foo_9(k=timeout): ... # normal functions are not checked -def foo_10(timeout): - ... +def foo_10(timeout): ... -def foo_11(timeout, /): - ... +def foo_11(timeout, /): ... -def foo_12(*, timeout): - ... +def foo_12(*, timeout): ... # ignore all functions with a decorator @anything.anything -async def foo_decorator_1(timeout): - ... +async def foo_decorator_1(timeout): ... @anything.anything -async def foo_decorator_2(*, timeout): - ... +async def foo_decorator_2(*, timeout): ... @anything -async def foo_decorator_3(timeout, /): - ... +async def foo_decorator_3(timeout, /): ... diff --git a/tests/eval_files/trio110.py b/tests/eval_files/trio110.py index 11aad54..2052f8d 100644 --- a/tests/eval_files/trio110.py +++ b/tests/eval_files/trio110.py @@ -41,8 +41,7 @@ async def foo(): while ...: await noerror.sleep() - async def sleep(): - ... + async def sleep(): ... while ...: await sleep() diff --git a/tests/eval_files/trio113_trio.py b/tests/eval_files/trio113_trio.py index 3b6cc4f..b03dda9 100644 --- a/tests/eval_files/trio113_trio.py +++ b/tests/eval_files/trio113_trio.py @@ -125,5 +125,4 @@ async def contextlib_import_alias_acm(): # code coverage for non-name, non-attribute decorator @None # type: ignore -async def foo4(): - ... +async def foo4(): ... diff --git a/tests/eval_files/trio114.py b/tests/eval_files/trio114.py index ea9fcd9..c4f7b5c 100644 --- a/tests/eval_files/trio114.py +++ b/tests/eval_files/trio114.py @@ -3,8 +3,7 @@ # ARG --startable-in-context-manager=foo -async def foo(task_status): - ... +async def foo(task_status): ... async def bar(task_status): # error: 0, "bar" @@ -25,16 +24,13 @@ async def foo3(*, task_status): # error: 0, "foo3" # don't error on pos-only parameter -async def foo4(task_status, /): - ... +async def foo4(task_status, /): ... -async def foo5(*task_status): - ... +async def foo5(*task_status): ... -async def foo6(**task_status): - ... +async def foo6(**task_status): ... def sync(): diff --git a/tests/eval_files/trio117.py b/tests/eval_files/trio117.py index 6185136..f03f7b9 100644 --- a/tests/eval_files/trio117.py +++ b/tests/eval_files/trio117.py @@ -39,8 +39,7 @@ def bar(x: MultiError): # TRIO117: 11, "MultiError" # args are not ast.Name's, so this one (surprisingly!) isn't a false positive # (though any use of the variable will be) -def foo(MultiError: int): - ... +def foo(MultiError: int): ... # only triggers on *trio*.MultiError diff --git a/tests/eval_files/trio910.py b/tests/eval_files/trio910.py index 8770788..c4aaa64 100644 --- a/tests/eval_files/trio910.py +++ b/tests/eval_files/trio910.py @@ -15,8 +15,7 @@ async def foo() -> Any: await foo() -def bar() -> Any: - ... +def bar() -> Any: ... # ARG --enable=TRIO910,TRIO911 @@ -24,8 +23,12 @@ def bar() -> Any: # function whose body solely consists of pass, ellipsis, or string constants is safe -async def foo_empty_1(): +# fmt: off +async def foo_empty_1a(): ... +async def foo_empty_1b(): ... +async def foo_empty_1c(): ...; ... +# fmt: on async def foo_empty_2(): @@ -116,19 +119,16 @@ def foo_normal_func_1(): return -def foo_normal_func_2(): - ... +def foo_normal_func_2(): ... # overload decorator @overload -async def foo_overload_1(_: bytes): - ... +async def foo_overload_1(_: bytes): ... @typing.overload -async def foo_overload_1(_: str): - ... +async def foo_overload_1(_: str): ... async def foo_overload_1(_: bytes | str): diff --git a/tests/eval_files/trio911.py b/tests/eval_files/trio911.py index 68f4a76..465b473 100644 --- a/tests/eval_files/trio911.py +++ b/tests/eval_files/trio911.py @@ -13,8 +13,11 @@ async def foo() -> Any: await foo() -def bar(*args) -> Any: - ... +def bar(*args) -> Any: ... + + +# mypy now treats `if ...` as `if True`, so we have another arbitrary function instead +def condition() -> Any: ... async def foo_yield_1(): @@ -83,7 +86,7 @@ async def foo_async_for(): # error: 0, "exit", Statement("yield", lineno+4) async def foo_async_for_2(): # error: 0, "exit", Statement("yield", lineno+2) async for i in trio.trick_pyright: yield - if ...: + if condition(): break @@ -179,7 +182,7 @@ async def foo_while_continue_1(): # error: 0, "exit", Statement("yield", lineno await foo() while foo(): yield # error: 8, "yield", Statement("yield", lineno) - if ...: + if condition(): continue await foo() @@ -192,7 +195,7 @@ async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno if foo(): continue await foo() - if ...: + if condition(): continue while foo(): yield # safe @@ -203,7 +206,7 @@ async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno # else might not run async def foo_while_break_1(): # error: 0, "exit", Statement("yield", lineno+6) while foo(): - if ...: + if condition(): break else: await foo() @@ -215,7 +218,7 @@ async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3) await foo() while foo(): yield # safe - if ...: + if condition(): break await foo() @@ -224,7 +227,7 @@ async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3) async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7) while foo(): await foo() - if ...: + if condition(): break # if it breaks, have checkpointed else: await foo() # runs if 0-iter @@ -234,7 +237,7 @@ async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7) # break at non-guaranteed checkpoint async def foo_while_break_4(): # error: 0, "exit", Statement("yield", lineno+7) while foo(): - if ...: + if condition(): break await foo() # might not run else: @@ -247,7 +250,7 @@ async def foo_while_break_5(): # error: 0, "exit", Statement("yield", lineno+12 await foo() while foo(): yield - if ...: + if condition(): break await foo() while foo(): @@ -263,12 +266,12 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11 await foo() while foo(): yield - if ...: + if condition(): break await foo() yield await foo() - if ...: + if condition(): break yield # error: 4, "yield", Statement("yield", lineno-8) @@ -276,7 +279,7 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11 async def foo_while_break_7(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+5) while foo(): await foo() - if ...: + if condition(): break yield break @@ -454,7 +457,7 @@ async def foo_try_10_no_except(): # if async def foo_if_1(): - if ...: + if condition(): yield # error: 8, "yield", Statement("function definition", lineno-2) await foo() else: @@ -464,7 +467,7 @@ async def foo_if_1(): async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6) await foo() - if ...: + if condition(): ... else: yield @@ -473,7 +476,7 @@ async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6) async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6) await foo() - if ...: + if condition(): yield else: ... @@ -483,7 +486,7 @@ async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6) async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7) await foo() yield - if ...: + if condition(): await foo() else: ... @@ -492,7 +495,7 @@ async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7) async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8) await foo() - if ...: + if condition(): yield await foo() else: @@ -503,7 +506,7 @@ async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8) async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8) await foo() - if ...: + if condition(): yield else: yield @@ -513,14 +516,14 @@ async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8) async def foo_if_7(): # error: 0, "exit", Statement("function definition", lineno) - if ...: + if condition(): await foo() yield await foo() async def foo_if_8(): # error: 0, "exit", Statement("function definition", lineno) - if ...: + if condition(): ... else: await foo() @@ -538,7 +541,7 @@ async def foo_ifexp_1(): # error: 0, "exit", Statement("yield", lineno+1) # err async def foo_ifexp_2(): # error: 0, "exit", Statement("yield", lineno+2) print( (yield) # error: 9, "yield", Statement("function definition", lineno-2) - if ... and await foo() + if condition() and await foo() else await foo() ) @@ -548,8 +551,7 @@ def foo_sync_1(): return -def foo_sync_2(): - ... +def foo_sync_2(): ... def foo_sync_3(): @@ -557,13 +559,13 @@ def foo_sync_3(): def foo_sync_4(): - if ...: + if condition(): return yield def foo_sync_5(): - if ...: + if condition(): return yield @@ -575,7 +577,7 @@ def foo_sync_6(): def foo_sync_7(): while foo(): - if ...: + if condition(): return yield @@ -629,7 +631,7 @@ async def foo_loop_static(): for _ in [1, 2, 3]: await foo() - if ...: + if condition(): break else: yield @@ -638,14 +640,14 @@ async def foo_loop_static(): # continue for _ in [1, 2, 3]: - if ...: + if condition(): continue await foo() yield # error: 4, "yield", Stmt("yield", line-7) # continue/else for _ in [1, 2, 3]: - if ...: + if condition(): continue await foo() else: @@ -655,7 +657,7 @@ async def foo_loop_static(): for _ in [1, 2, 3]: await foo() - if ...: + if condition(): break else: yield @@ -776,21 +778,21 @@ async def foo_loop_static(): # while while True: await foo() - if ...: + if condition(): break yield while True: - if ...: + if condition(): break await foo() yield # error: 4, "yield", Stmt("yield", line-6) while True: - if ...: + if condition(): continue await foo() - if ...: + if condition(): break yield diff --git a/tests/eval_files/trio91x_autofix.py b/tests/eval_files/trio91x_autofix.py index 8581b9b..c523ffb 100644 --- a/tests/eval_files/trio91x_autofix.py +++ b/tests/eval_files/trio91x_autofix.py @@ -11,8 +11,7 @@ from typing import Any -def bar() -> Any: - ... +def bar() -> Any: ... async def foo() -> Any: @@ -34,9 +33,9 @@ async def foo_yield(): # TRIO911: 0, "exit", Statement("yield", lineno+2) async def foo_if(): - if ...: + if foo(): return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif ...: + elif foo(): return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: return # TRIO910: 8, "return", Statement("function definition", lineno-6) @@ -58,7 +57,7 @@ async def foo_while2(): async def foo_while3(): await foo() while True: - if ...: + if foo(): return await foo() @@ -66,9 +65,9 @@ async def foo_while3(): # check that multiple checkpoints don't get inserted async def foo_while4(): while True: - if ...: + if foo(): yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if ...: + if foo(): yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways @@ -88,7 +87,7 @@ async def foo_while_nested_func(): yield # TRIO911: 8, "yield", Statement("function definition", lineno-2) # TRIO911: 8, "yield", Statement("yield", lineno) async def bar(): - while ...: + while foo(): ... await foo() @@ -96,17 +95,16 @@ async def bar(): # Code coverage: visitors run when inside a sync function that has an async function. # When sync funcs don't contain an async func the body is not visited. def sync_func(): - async def async_func(): - ... + async def async_func(): ... try: ... except: ... - if ... and ...: + if foo() and foo(): ... while ...: - if ...: + if foo(): continue break [... for i in range(5)] diff --git a/tests/test_config_and_args.py b/tests/test_config_and_args.py index 7e14c72..e3449ef 100644 --- a/tests/test_config_and_args.py +++ b/tests/test_config_and_args.py @@ -1,4 +1,5 @@ """Various tests for testing argument and config parsing.""" + from __future__ import annotations import ast @@ -55,6 +56,7 @@ def test_run_flake8_trio(tmp_path: Path): ], cwd=tmp_path, capture_output=True, + check=False, ) assert res.returncode == 1 assert not res.stderr @@ -94,14 +96,15 @@ def test_systemexit_1( def test_run_in_git_repo(tmp_path: Path): write_examplepy(tmp_path) - assert subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True) - assert subprocess.run(["git", "add", "example.py"], cwd=tmp_path) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) + subprocess.run(["git", "add", "example.py"], cwd=tmp_path, check=True) res = subprocess.run( [ "flake8-trio", ], cwd=tmp_path, capture_output=True, + check=False, ) assert res.returncode == 1 assert not res.stderr @@ -240,7 +243,7 @@ def test_200_from_config_flake8_internals( def test_200_from_config_subprocess(tmp_path: Path): err_msg = _test_trio200_from_config_common(tmp_path) - res = subprocess.run(["flake8"], cwd=tmp_path, capture_output=True) + res = subprocess.run(["flake8"], cwd=tmp_path, capture_output=True, check=False) assert res.returncode == 1 assert not res.stderr assert res.stdout == err_msg.encode("ascii") @@ -269,8 +272,7 @@ def test_enable( monkeypatch_argv(monkeypatch, tmp_path, argv) def _helper(*args: str, error: bool = False, autofix: bool = False) -> None: - for arg in args: - argv.append(arg) + argv.extend(args) main() out, err = capsys.readouterr() if error: @@ -333,7 +335,9 @@ def test_flake8_plugin_with_autofix_fails(tmp_path: Path): ], cwd=tmp_path, capture_output=True, + check=False, ) + assert res.returncode == 1 assert not res.stdout assert res.stderr == b"Cannot autofix when run as a flake8 plugin.\n" @@ -365,8 +369,7 @@ def test_disable_noqa_cst( out, err = capsys.readouterr() assert not err assert ( - out - == "./example.py:2:6: TRIO100 trio.move_on_after context contains no" + out == "./example.py:2:6: TRIO100 trio.move_on_after context contains no" " checkpoints, remove the context or add `await" " trio.lowlevel.checkpoint()`.\n" ) diff --git a/tests/test_flake8_trio.py b/tests/test_flake8_trio.py index cc6dfa3..7d95e88 100644 --- a/tests/test_flake8_trio.py +++ b/tests/test_flake8_trio.py @@ -47,8 +47,7 @@ ), f"no eval file for autofix file[s] {extra_autofix_files}" -class ParseError(Exception): - ... +class ParseError(Exception): ... # check for presence of _pyXX, skip if version is later, and prune parameter @@ -500,11 +499,11 @@ def print_first_diff(errors: Sequence[Error], expected: Sequence[Error]): def assert_correct_lines_and_codes(errors: Iterable[Error], expected: Iterable[Error]): """Check that errors are on correct lines.""" - MyDict = defaultdict[int, defaultdict[str, int]] # TypeAlias - all_lines = sorted({e.line for e in (*errors, *expected)}) - error_dict: MyDict = defaultdict(lambda: defaultdict(int)) + error_dict: defaultdict[int, defaultdict[str, int]] = defaultdict( + lambda: defaultdict(int) + ) expected_dict = copy.deepcopy(error_dict) # populate dicts with number of errors per line @@ -589,7 +588,8 @@ def info_tuple(error: Error): for err, exp in zip(errors, expected): err_msg = info_tuple(err) for err, type_ in zip(err_msg, (int, int, str, type(None))): - assert isinstance(err, type_) + # mypy fails to track types across the zip + assert isinstance(err, type_) # type: ignore[arg-type] assert err_msg == info_tuple(exp) @@ -751,5 +751,5 @@ def test_does_not_crash_on_site_code(enable_codes: str): plugin = Plugin.from_filename(str(path)) initialize_options(plugin, [f"--enable={enable_codes}"]) consume(plugin.run()) - except Exception as err: + except Exception as err: # noqa: PERF203 # try-except in loop raise AssertionError(f"Failed on {path}") from err diff --git a/tox.ini b/tox.ini index 35086d9..1ac13e0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ # The test environment and commands [tox] # default environments to run without `-e` -envlist = py{39,310,311}-{flake8_5,flake8_6} +envlist = py{39,310,311,312}-{flake8_5,flake8_6} # create a default testenv, whose behaviour will depend on the name it's called with. # for CI you can call with `-e flake8_5,flake8_6` and let the CI handle python version @@ -16,6 +16,7 @@ deps = hypothesis hypothesmith trio + ipdb commands = pytest {posargs} #{posargs:-n auto} @@ -45,18 +46,3 @@ exclude_lines = # Don't check guarded type imports if (typing.)?TYPE_CHECKING: - -[flake8] -max-line-length = 90 -# not supported by ruff + want to `noqa` them, so instead ignoring them: PIE786, R504 -extend-ignore = S101, D101, D102, D103, D105, D106, D107, PIE786, R504 -extend-enable = TC10 -exclude = .*, tests/eval_files/*, tests/autofix_files/* -per-file-ignores = - flake8_trio/visitors/__init__.py: F401, E402 -# visitor_utility contains comments specifying how it parses noqa comments, which get -# parsed as noqa comments - flake8_trio/visitors/visitor_utility.py: NQA101, NQA102 -# (E301, E302) black formats stub files without excessive blank lines -# (D) we don't care about docstrings in stub files - *.pyi: D, E301, E302 diff --git a/typings/flake8/main/application.pyi b/typings/flake8/main/application.pyi index b8ffe9a..3551a77 100644 --- a/typings/flake8/main/application.pyi +++ b/typings/flake8/main/application.pyi @@ -13,29 +13,36 @@ class Application: def __init__(self) -> None: """Initialize our application.""" ... + def exit_code(self) -> int: """Return the program exit code.""" ... + def make_formatter(self) -> None: """Initialize a formatter based on the parsed options.""" ... + def make_guide(self) -> None: """Initialize our StyleGuide.""" ... + def make_file_checker_manager(self, argv: Sequence[str]) -> None: """Initialize our FileChecker Manager.""" ... + def run_checks(self) -> None: """Run the actual checks with the FileChecker Manager. This method encapsulates the logic to make a - :class:`~flake8.checker.Manger` instance run the checks it is + :class:`~flake8.checker.Manager` instance run the checks it is managing. """ ... + def report_benchmarks(self) -> None: """Aggregate, calculate, and report benchmarks for this run.""" ... + def report_errors(self) -> None: """Report all the errors found by flake8 3.0. @@ -43,9 +50,11 @@ class Application: number of errors, warnings, and other messages found. """ ... + def report_statistics(self) -> None: """Aggregate and report statistics from this run.""" ... + def initialize(self, argv: Sequence[str]) -> None: """Initialize the application to be run. @@ -53,9 +62,11 @@ class Application: command-line arguments. """ ... + def report(self) -> None: """Report errors, statistics, and benchmarks.""" ... + def run(self, argv: Sequence[str]) -> None: """Run our application. diff --git a/typings/flake8/options/manager.pyi b/typings/flake8/options/manager.pyi index 914a91c..3d68eb8 100644 --- a/typings/flake8/options/manager.pyi +++ b/typings/flake8/options/manager.pyi @@ -123,13 +123,16 @@ class Option: attempt to normalize the paths to absolute paths. """ ... + @property def filtered_option_kwargs(self) -> dict[str, Any]: """Return any actually-specified arguments.""" ... + def normalize(self, value: Any, *normalize_args: str) -> Any: """Normalize the value based on the option configuration.""" ... + def to_argparse(self) -> tuple[list[str], dict[str, Any]]: """Convert a Flake8 Option to argparse ``add_argument`` arguments.""" ... @@ -157,9 +160,11 @@ class OptionManager: included. """ ... + def register_plugins(self, plugins: Plugins) -> None: """Register the plugin options (if needed).""" ... + def add_option(self, *args: Any, **kwargs: Any) -> None: """Create and register a new option. @@ -172,6 +177,7 @@ class OptionManager: positionally as they are with argparse normally. """ ... + def extend_default_ignore(self, error_codes: Sequence[str]) -> None: """Extend the default ignore list with the error codes provided. @@ -180,6 +186,7 @@ class OptionManager: extend the default ignore list. """ ... + def extend_default_select(self, error_codes: Sequence[str]) -> None: """Extend the default select list with the error codes provided. @@ -188,6 +195,7 @@ class OptionManager: to extend the default select list. """ ... + def parse_args( self, args: Sequence[str] | None = ..., From 3cf9d97c4b75c5d0b781ff8570eb5b2bcb4c4ceb Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 10:48:15 +0100 Subject: [PATCH 02/10] undo pyupgrade breaking trio103 eval file, exclude it in pre-commit revert adding ipdb to tox (was briefly used when testing stuff) --- .pre-commit-config.yaml | 1 + tests/eval_files/trio103.py | 5 +++-- tox.ini | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 598ce1d..af32387 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,7 @@ repos: hooks: - id: pyupgrade args: [--py39-plus] + exclude: tests/eval_files/trio103.py - repo: https://github.com/pycqa/isort rev: 5.13.2 diff --git a/tests/eval_files/trio103.py b/tests/eval_files/trio103.py index 52829fc..a4e39be 100644 --- a/tests/eval_files/trio103.py +++ b/tests/eval_files/trio103.py @@ -198,8 +198,9 @@ def foo() -> Any: ... except ( my_super_mega_long_exception_so_it_gets_split, SyntaxError, - BaseException, # TRIO103_trio: 4, "BaseException" - ValueError, # no complaint on this line + BaseException, # TRIO103_trio: 4, "BaseException" + ValueError, + BaseException, # no complaint on this line ): ... diff --git a/tox.ini b/tox.ini index 1ac13e0..c7fd716 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,6 @@ deps = hypothesis hypothesmith trio - ipdb commands = pytest {posargs} #{posargs:-n auto} From 7711b928dc8c8e9e301ee8be774e67352034d46d Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 12:43:48 +0100 Subject: [PATCH 03/10] bump minimum flake8 version, and bump libcst version to match reality --- .github/workflows/ci.yml | 10 +++++----- setup.py | 2 +- tox.ini | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 924f919..d74bbb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,11 +36,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools tox - python -m tox --notest --recreate -e flake8_5,flake8_6 - - name: Run tests - run: python -m tox -e flake8_5,flake8_6 + run: python -m pip install --upgrade pip setuptools tox + - name: Run tests with flake8_6 + run: python -m tox -e flake8_6 + - name: Run tests with flake8_7+ + run: python -m tox -e flake8_7 slow_tests: runs-on: ubuntu-latest diff --git a/setup.py b/setup.py index 22a26ed..bbc41fe 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def local_file(name: str) -> Path: license="MIT", description="A highly opinionated flake8 plugin for Trio-related problems.", zip_safe=False, - install_requires=["flake8>=5", "libcst>=0.4"], + install_requires=["flake8>=6", "libcst>=1.0.1"], python_requires=">=3.9", classifiers=[ "Development Status :: 3 - Alpha", diff --git a/tox.ini b/tox.ini index c7fd716..fd02161 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,8 @@ envlist = py{39,310,311,312}-{flake8_5,flake8_6} [testenv] description = Runs pytest, optionally with posargs deps = - flake8_6: flake8>=6.0 - flake8_5: flake8>=5.0,<6.0 + flake8_7: flake8>=7.0 + flake8_6: flake8>=6.0, <7.0 pytest pytest-cov pytest-xdist From afdea1396594078c333e7cd24b86832ea45ee8d7 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 12:45:07 +0100 Subject: [PATCH 04/10] remove 3.13 from CI, pending hyposmith being importable on it. Update tags in setup.py to specify we support 3.12 --- .github/workflows/ci.yml | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d74bbb6..386b47c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.9', '3.10', '3.11', '3.12'] fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index bbc41fe..a6bdf55 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ def local_file(name: str) -> Path: "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], long_description=( local_file("README.md").open().read() From 38d6281d3da23f679203799499eab0fd8bacffa6 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 12:46:54 +0100 Subject: [PATCH 05/10] start using pytest-xdist in CI, let's hope it's faster than 90s --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fd02161..33d307d 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ deps = hypothesmith trio commands = - pytest {posargs} #{posargs:-n auto} + pytest {posargs:-n auto} # Settings for other tools [pytest] From dde4479842315a6a64f2f56d3875931ead9f0af3 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 15:09:51 +0100 Subject: [PATCH 06/10] use flake8_7 with CI --run-slow, fix typing issues --- .github/workflows/ci.yml | 4 ++-- flake8_trio/visitors/helpers.py | 6 ++++-- flake8_trio/visitors/visitor_utility.py | 8 ++++++-- pyproject.toml | 1 + tests/autofix_files/trio910.py | 2 ++ tests/eval_files/trio200.py | 4 ++-- tests/eval_files/trio910.py | 1 + tests/eval_files/trio91x_noautofix.py | 5 ++++- tests/test_changelog_and_version.py | 2 ++ 9 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 386b47c..a76059d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,9 +55,9 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox - python -m tox --notest --recreate -e flake8_6 + python -m tox --notest --recreate -e flake8_7 - name: Run tests - run: python -m tox -e flake8_6 -- --onlyfuzz --no-cov -n auto + run: python -m tox -e flake8_7 -- --onlyfuzz --no-cov -n auto release: runs-on: ubuntu-latest diff --git a/flake8_trio/visitors/helpers.py b/flake8_trio/visitors/helpers.py index 03b7eff..d953e2e 100644 --- a/flake8_trio/visitors/helpers.py +++ b/flake8_trio/visitors/helpers.py @@ -7,7 +7,7 @@ import ast from fnmatch import fnmatch -from typing import TYPE_CHECKING, NamedTuple, TypeVar +from typing import TYPE_CHECKING, NamedTuple, TypeVar, Union import libcst as cst import libcst.matchers as m @@ -29,7 +29,9 @@ T = TypeVar("T", bound=Flake8TrioVisitor) T_CST = TypeVar("T_CST", bound=Flake8TrioVisitor_cst) - T_EITHER = TypeVar("T_EITHER", bound=Flake8TrioVisitor | Flake8TrioVisitor_cst) + T_EITHER = TypeVar( + "T_EITHER", bound=Union[Flake8TrioVisitor, Flake8TrioVisitor_cst] + ) def error_class(error_class: type[T]) -> type[T]: diff --git a/flake8_trio/visitors/visitor_utility.py b/flake8_trio/visitors/visitor_utility.py index a2f31e7..35efe59 100644 --- a/flake8_trio/visitors/visitor_utility.py +++ b/flake8_trio/visitors/visitor_utility.py @@ -5,7 +5,7 @@ import ast import functools import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import libcst.matchers as m from libcst.metadata import PositionProvider @@ -17,6 +17,7 @@ from re import Match import libcst as cst + from libcst._position import CodeRange @utility_visitor @@ -178,7 +179,10 @@ def visit_Comment(self, node: cst.Comment): return False codes_str = noqa_match.groupdict()["codes"] - pos = self.get_metadata(PositionProvider, node).start + + # see https://github.com/Instagram/LibCST/issues/1107 + metadata = cast("CodeRange", self.get_metadata(PositionProvider, node)) + pos = metadata.start codes: set[str] diff --git a/pyproject.toml b/pyproject.toml index 579b6ed..38f6ec5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ skip_glob = "tests/eval_files/*" [tool.mypy] check_untyped_defs = true disable_error_code = ["no-untyped-def", "misc", "no-untyped-call", "no-any-return"] +python_version = "3.9" strict = true warn_unreachable = true warn_unused_ignores = false diff --git a/tests/autofix_files/trio910.py b/tests/autofix_files/trio910.py index bd752dd..3e23199 100644 --- a/tests/autofix_files/trio910.py +++ b/tests/autofix_files/trio910.py @@ -1,5 +1,7 @@ # AUTOFIX # mypy: disable-error-code="unreachable" +from __future__ import annotations + import typing from typing import Any, overload diff --git a/tests/eval_files/trio200.py b/tests/eval_files/trio200.py index 93ee5c5..7526ccb 100644 --- a/tests/eval_files/trio200.py +++ b/tests/eval_files/trio200.py @@ -37,10 +37,10 @@ async def afoo(): lambda: bar() # check that states are properly set/reset on nested functions - def bar(): + def bar2(): bar() - async def bar(): + async def bar3(): bar() # TRIO200: 12, "bar", "BAR" bar() # TRIO200: 4, "bar", "BAR" diff --git a/tests/eval_files/trio910.py b/tests/eval_files/trio910.py index c4aaa64..794744c 100644 --- a/tests/eval_files/trio910.py +++ b/tests/eval_files/trio910.py @@ -1,5 +1,6 @@ # AUTOFIX # mypy: disable-error-code="unreachable" +from __future__ import annotations import typing from typing import Any, overload diff --git a/tests/eval_files/trio91x_noautofix.py b/tests/eval_files/trio91x_noautofix.py index 1f29e62..652f3dd 100644 --- a/tests/eval_files/trio91x_noautofix.py +++ b/tests/eval_files/trio91x_noautofix.py @@ -2,6 +2,9 @@ from typing import Any +def condition() -> Any: ... + + async def foo() -> Any: await foo() @@ -40,7 +43,7 @@ async def foo_async_with_2(): # fmt: off async def foo_boolops_3(): _ = (await foo() or (yield) or await foo()) or ( - ... + condition() or ( (yield) # TRIO911: 13, "yield", Stmt("yield", line-3) and (yield)) # TRIO911: 17, "yield", Stmt("yield", line-1) diff --git a/tests/test_changelog_and_version.py b/tests/test_changelog_and_version.py index 46358dd..2a53ad2 100755 --- a/tests/test_changelog_and_version.py +++ b/tests/test_changelog_and_version.py @@ -36,6 +36,8 @@ def __str__(self) -> str: if m := re.match(r'__version__ = "(\d*\.\d*\.\d*)"', line): VERSION = Version.from_string(m.groups()[0]) break +else: + raise RuntimeError("No version detected.") def get_releases() -> Iterable[Version]: From fe2124ce87fb44f8a0ac18a4f96319abf99dc7ba Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 15:15:41 +0100 Subject: [PATCH 07/10] isort messed up autofix diff file --- tests/eval_files/trio910.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/eval_files/trio910.py b/tests/eval_files/trio910.py index 794744c..5a4c372 100644 --- a/tests/eval_files/trio910.py +++ b/tests/eval_files/trio910.py @@ -1,6 +1,7 @@ # AUTOFIX # mypy: disable-error-code="unreachable" from __future__ import annotations + import typing from typing import Any, overload From c124786326677b75cd93cc3521d68317444f1691 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 15 Feb 2024 15:25:30 +0100 Subject: [PATCH 08/10] bump default_language_version to 3.12 in pre-commit-config --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af32387..5297280 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ --- default_language_version: - python: python3.11 + python: python3.12 # pyright requires internet connection to run, which the pre-commit ci app doesn't have. # Not used in this repo. ci: From 41539a0eafe1de8f087879098a0a0f9b1377338a Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 16 Feb 2024 13:38:12 +0100 Subject: [PATCH 09/10] remove unnecessary pyright: ignore --- flake8_trio/visitors/flake8triovisitor.py | 6 ++---- pyproject.toml | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake8_trio/visitors/flake8triovisitor.py b/flake8_trio/visitors/flake8triovisitor.py index 4c4bf48..fdf5bf8 100644 --- a/flake8_trio/visitors/flake8triovisitor.py +++ b/flake8_trio/visitors/flake8triovisitor.py @@ -23,9 +23,7 @@ class Flake8TrioVisitor(ast.NodeVisitor, ABC): # abstract attribute by not providing a value - error_codes: ClassVar[ - dict[str, str] - ] # pyright: ignore[reportUninitializedInstanceVariable] + error_codes: ClassVar[dict[str, str]] def __init__(self, shared_state: SharedState): super().__init__() @@ -160,7 +158,7 @@ def add_library(self, name: str) -> None: class Flake8TrioVisitor_cst(cst.CSTTransformer, ABC): # abstract attribute by not providing a value - error_codes: dict[str, str] # pyright: ignore[reportUninitializedInstanceVariable] + error_codes: dict[str, str] METADATA_DEPENDENCIES = (PositionProvider,) def __init__(self, shared_state: SharedState): diff --git a/pyproject.toml b/pyproject.toml index 38f6ec5..f0a4e3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,8 @@ reportMissingSuperCall = true reportPropertyTypeMismatch = true reportShadowedImports = true reportUninitializedInstanceVariable = true +# can't enable until https://github.com/python/mypy/issues/12358 +reportUnnecessaryTypeIgnoreComment = false reportUnusedCallResult = false strict = ["*.py", "tests/*.py", "flake8_trio/**/*.py"] From 6f0f574cf4fa85d284da2cdd327630099726710c Mon Sep 17 00:00:00 2001 From: jakkdl Date: Mon, 19 Feb 2024 09:32:57 +0100 Subject: [PATCH 10/10] foo-> bar in some test files, replace a couple instances of flake8_5 I missed, add py313 support now that hypothesmith is updated --- .github/workflows/ci.yml | 2 +- setup.py | 1 + tests/autofix_files/trio91x_autofix.py | 16 ++++++++-------- tests/autofix_files/trio91x_autofix.py.diff | 8 ++++---- tests/eval_files/trio91x_autofix.py | 16 ++++++++-------- tox.ini | 7 ++++--- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a76059d..1368e30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12', 3.13-dev] fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index a6bdf55..86db921 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ def local_file(name: str) -> Path: "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], long_description=( local_file("README.md").open().read() diff --git a/tests/autofix_files/trio91x_autofix.py b/tests/autofix_files/trio91x_autofix.py index 685608c..aa58e88 100644 --- a/tests/autofix_files/trio91x_autofix.py +++ b/tests/autofix_files/trio91x_autofix.py @@ -38,10 +38,10 @@ async def foo_yield(): # TRIO911: 0, "exit", Statement("yield", lineno+2) async def foo_if(): - if foo(): + if bar(): await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif foo(): + elif bar(): await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: @@ -66,7 +66,7 @@ async def foo_while2(): async def foo_while3(): await foo() while True: - if foo(): + if bar(): return await foo() @@ -74,10 +74,10 @@ async def foo_while3(): # check that multiple checkpoints don't get inserted async def foo_while4(): while True: - if foo(): + if bar(): await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if foo(): + if bar(): await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways @@ -102,7 +102,7 @@ async def foo_while_nested_func(): yield # TRIO911: 8, "yield", Statement("function definition", lineno-2) # TRIO911: 8, "yield", Statement("yield", lineno) async def bar(): - while foo(): + while bar(): ... await foo() @@ -116,10 +116,10 @@ async def async_func(): ... ... except: ... - if foo() and foo(): + if bar() and bar(): ... while ...: - if foo(): + if bar(): continue break [... for i in range(5)] diff --git a/tests/autofix_files/trio91x_autofix.py.diff b/tests/autofix_files/trio91x_autofix.py.diff index 08a2ea8..3b22f13 100644 --- a/tests/autofix_files/trio91x_autofix.py.diff +++ b/tests/autofix_files/trio91x_autofix.py.diff @@ -29,10 +29,10 @@ async def foo_if(): - if foo(): + if bar(): + await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif foo(): + elif bar(): + await trio.lowlevel.checkpoint() return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: @@ -50,10 +50,10 @@ @@ x,8 x,10 @@ async def foo_while4(): while True: - if foo(): + if bar(): + await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if foo(): + if bar(): + await trio.lowlevel.checkpoint() yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways diff --git a/tests/eval_files/trio91x_autofix.py b/tests/eval_files/trio91x_autofix.py index c523ffb..78b3fc4 100644 --- a/tests/eval_files/trio91x_autofix.py +++ b/tests/eval_files/trio91x_autofix.py @@ -33,9 +33,9 @@ async def foo_yield(): # TRIO911: 0, "exit", Statement("yield", lineno+2) async def foo_if(): - if foo(): + if bar(): return # TRIO910: 8, "return", Statement("function definition", lineno-2) - elif foo(): + elif bar(): return # TRIO910: 8, "return", Statement("function definition", lineno-4) else: return # TRIO910: 8, "return", Statement("function definition", lineno-6) @@ -57,7 +57,7 @@ async def foo_while2(): async def foo_while3(): await foo() while True: - if foo(): + if bar(): return await foo() @@ -65,9 +65,9 @@ async def foo_while3(): # check that multiple checkpoints don't get inserted async def foo_while4(): while True: - if foo(): + if bar(): yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno+2) # TRIO911: 12, "yield", Statement("function definition", lineno-3) - if foo(): + if bar(): yield # TRIO911: 12, "yield", Statement("yield", lineno) # TRIO911: 12, "yield", Statement("yield", lineno-2) # TRIO911: 12, "yield", Statement("function definition", lineno-5) # TRIO911: 12, "yield", Statement("yield", lineno-2) # this warns about the yield on lineno-2 twice, since it can arrive here from it in two different ways @@ -87,7 +87,7 @@ async def foo_while_nested_func(): yield # TRIO911: 8, "yield", Statement("function definition", lineno-2) # TRIO911: 8, "yield", Statement("yield", lineno) async def bar(): - while foo(): + while bar(): ... await foo() @@ -101,10 +101,10 @@ async def async_func(): ... ... except: ... - if foo() and foo(): + if bar() and bar(): ... while ...: - if foo(): + if bar(): continue break [... for i in range(5)] diff --git a/tox.ini b/tox.ini index 33d307d..1222de8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,10 @@ # The test environment and commands [tox] # default environments to run without `-e` -envlist = py{39,310,311,312}-{flake8_5,flake8_6} +envlist = py{39,310,311,312,313}-{flake8_6,flake8_7} # create a default testenv, whose behaviour will depend on the name it's called with. -# for CI you can call with `-e flake8_5,flake8_6` and let the CI handle python version +# for CI you can call with `-e flake8_6,flake8_7` and let the CI handle python version [testenv] description = Runs pytest, optionally with posargs deps = @@ -14,7 +14,8 @@ deps = pytest-cov pytest-xdist hypothesis - hypothesmith + # 0.3.3 adds py313 support + hypothesmith >= 0.3.3 trio commands = pytest {posargs:-n auto}