diff --git a/CHANGELOG.md b/CHANGELOG.md index 3936927f..8976aa48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog *[CalVer, YY.month.patch](https://calver.org/)* +## 23.1.4 +- TRIO114 no longer triggers on posonly args named "task_status" +- TRIO116 will now match on any attribute parameter named `.inf`, not just `math.inf`. + ## 23.1.3 - Add TRIO240: usage of `os.path` in async function. - Add TRIO900: ban async generators not decorated with known safe decorator diff --git a/README.md b/README.md index d1570ee5..df41a292 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ pip install flake8-trio - **TRIO111**: Variable, from context manager opened inside nursery, passed to `start[_soon]` might be invalidly accesed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager. - **TRIO112**: nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call. - **TRIO113**: using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`. -- **TRIO114**: Startable function (i.e. has a `task_status` parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it. +- **TRIO114**: Startable function (i.e. has a `task_status` keyword parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it. - **TRIO115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`. - **TRIO116**: `trio.sleep()` with >24 hour interval should usually be`trio.sleep_forever()`. - **TRIO200**: User-configured error for blocking sync calls in async functions. Does nothing by default, see [`trio200-blocking-calls`](#trio200-blocking-calls) for how to configure it. diff --git a/flake8_trio/__init__.py b/flake8_trio/__init__.py index 5b3a8b11..b8029658 100644 --- a/flake8_trio/__init__.py +++ b/flake8_trio/__init__.py @@ -31,7 +31,7 @@ # CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1" -__version__ = "23.1.3" +__version__ = "23.1.4" class Plugin: @@ -92,7 +92,7 @@ def add_options(option_manager: OptionManager): parse_from_config=True, required=False, help=( - "Comma-separated list of key:value pairs, where key is a [dotted] " + "Comma-separated list of key->value pairs, where key is a [dotted] " "function that if found inside an async function will raise TRIO200, " "suggesting it be replaced with {value}" ), diff --git a/flake8_trio/base.py b/flake8_trio/base.py index 1daeaca3..fda46fcd 100644 --- a/flake8_trio/base.py +++ b/flake8_trio/base.py @@ -36,6 +36,8 @@ def __iter__(self): yield self.line yield self.col yield f"{self.code} " + self.message.format(*self.args) + # We are no longer yielding `type(Plugin)` since that's quite tricky to do + # without circular imports, and afaik flake8 doesn't care anymore. yield None def cmp(self): diff --git a/flake8_trio/visitors/visitors.py b/flake8_trio/visitors/visitors.py index 861e0389..d2dd94ff 100644 --- a/flake8_trio/visitors/visitors.py +++ b/flake8_trio/visitors/visitors.py @@ -23,7 +23,8 @@ class Visitor100(Flake8TrioVisitor): error_codes = { "TRIO100": ( - "{} context contains no checkpoints, add `await trio.lowlevel.checkpoint()`" + "{} context contains no checkpoints, remove the context or add" + " `await trio.lowlevel.checkpoint()`" ), } @@ -255,8 +256,8 @@ def _get_identifier(node: ast.expr) -> str: class Visitor113(Flake8TrioVisitor): error_codes = { "TRIO113": ( - "Dangerous `.start_soon()`, process might not run before `__aenter__` " - "exits. Consider replacing with `.start()`." + "Dangerous `.start_soon()`, function might not be executed before" + " `__aenter__` exits. Consider replacing with `.start()`." ), } @@ -315,9 +316,7 @@ class Visitor114(Flake8TrioVisitor): def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef): if any( isinstance(n, ast.arg) and n.arg == "task_status" - for n in self.walk( - *node.args.args, *node.args.posonlyargs, *node.args.kwonlyargs - ) + for n in self.walk(*node.args.args, *node.args.kwonlyargs) ) and not any( node.name == opt for opt in (*self.options.startable_in_context_manager, *STARTABLE_CALLS) @@ -357,12 +356,7 @@ def visit_Call(self, node: ast.Call): arg = node.args[0] if ( # `trio.sleep(math.inf)` - ( - isinstance(arg, ast.Attribute) - and isinstance(arg.value, ast.Name) - and arg.attr == "inf" - and arg.value.id == "math" - ) + (isinstance(arg, ast.Attribute) and arg.attr == "inf") # `trio.sleep(inf)` or (isinstance(arg, ast.Name) and arg.id == "inf") # `trio.sleep(float("inf"))` diff --git a/setup.py b/setup.py index 7392b97d..03ec4c72 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def local_file(name: str) -> Path: setup( name="flake8-trio", version=__version__, - author="Zac Hatfield-Dodds and Contributors", + author="Zac Hatfield-Dodds, John Litborn, and Contributors", author_email="zac@zhd.dev", packages=find_packages(include=["flake8_trio", "flake8_trio.*"]), url="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/Zac-HD/flake8-trio", diff --git a/tests/eval_files/trio114.py b/tests/eval_files/trio114.py index 7c245c83..ea9fcd9b 100644 --- a/tests/eval_files/trio114.py +++ b/tests/eval_files/trio114.py @@ -24,7 +24,8 @@ async def foo3(*, task_status): # error: 0, "foo3" ... -async def foo4(task_status, /): # error: 0, "foo4" +# don't error on pos-only parameter +async def foo4(task_status, /): ... diff --git a/tests/eval_files/trio116.py b/tests/eval_files/trio116.py index cc1d2ccd..7cf25675 100644 --- a/tests/eval_files/trio116.py +++ b/tests/eval_files/trio116.py @@ -12,6 +12,7 @@ async def foo(): await trio.sleep(float("inf")) # error: 10 await trio.sleep(math.inf) # error: 10 await trio.sleep(inf) # error: 10 + # 'inf literal' overflow trick await trio.sleep(1e999) # error: 10 @@ -32,6 +33,13 @@ async def foo(): await trio.sleep("hello") await trio.sleep(...) + # don't require inf to be in math #103 + await trio.sleep(np.inf) # error: 10 + await trio.sleep(anything.inf) # error: 10 + await trio.sleep(anything.anything.inf) # error: 10 + await trio.sleep(inf.inf) # error: 10 + await trio.sleep(inf.anything) + # does not require the call to be awaited, nor in an async fun trio.sleep(86401) # error: 0 diff --git a/tests/test_all_visitors_imported.py b/tests/test_all_visitors_imported.py new file mode 100644 index 00000000..7c153192 --- /dev/null +++ b/tests/test_all_visitors_imported.py @@ -0,0 +1,23 @@ +"""Check that all visitors are imported. + +Checks that all flake8_trio/visitor*.py files are imported in flake8_trio/visitor/__init__ +so their decorators are run. +""" + +from __future__ import annotations + +import re +from pathlib import Path + + +def test_all_visitors_imported(): + visitor_dir = Path(__file__).parent.parent / "flake8_trio" / "visitors" + visitor_files = { + f.stem for f in visitor_dir.iterdir() if f.stem.startswith("visitor") + } + visited_files = set() + with open(visitor_dir / "__init__.py") as f: + for line in f: + if m := re.match(r"from \. import (?P\w*)", line): + visited_files.add(m.group("module")) + assert visitor_files == visited_files diff --git a/tests/test_decorator.py b/tests/test_decorator.py index 5cd18ee2..726bb319 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -96,9 +96,9 @@ def test_command_line_1(capfd): expected_lineno = -1 with open(file_path) as f: - for lineno, line in enumerate(f): + for lineno, line in enumerate(f, start=1): if line.startswith("async def"): - expected_lineno = lineno + 1 + expected_lineno = lineno break expected_out = ( diff --git a/tests/test_flake8_trio.py b/tests/test_flake8_trio.py index a891b569..ffc3795a 100644 --- a/tests/test_flake8_trio.py +++ b/tests/test_flake8_trio.py @@ -249,7 +249,7 @@ def print_first_diff(errors: Sequence[Error], expected: Sequence[Error]): if first_expected_line != first_error_line: print( - "First lines with different errors", + "\nFirst lines with different errors", f" actual: {[e.cmp() for e in first_error_line]}", f"expected: {[e.cmp() for e in first_expected_line]}", "",