From ff82924005a3be8bb26ef2a43faad8b26dd5741b Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 20:26:15 -0400 Subject: [PATCH 01/22] rename rules and stabilize tests --- .../test/fixtures/flake8_async/ASYNC100.py | 30 +++-- .../TRIO105.py => flake8_async/ASYNC105.py} | 6 +- .../TRIO109.py => flake8_async/ASYNC109.py} | 0 .../TRIO110.py => flake8_async/ASYNC110.py} | 0 .../TRIO115.py => flake8_async/ASYNC115.py} | 12 +- .../test/fixtures/flake8_async/ASYNC210.py | 23 ++++ .../flake8_async/{ASYNC101.py => ASYNC220.py} | 10 +- .../flake8_async/{ASYNC102.py => ASYNC222.py} | 0 .../test/fixtures/flake8_trio/TRIO100.py | 27 ---- crates/ruff_linter/src/codes.rs | 18 ++- crates/ruff_linter/src/registry.rs | 3 - .../ruff_linter/src/rules/flake8_async/mod.rs | 6 +- ...e8_async__tests__ASYNC100_ASYNC100.py.snap | 39 ------ ...e8_async__tests__ASYNC101_ASYNC101.py.snap | 84 ------------ ...e8_async__tests__ASYNC210_ASYNC210.py.snap | 37 ++++++ ...e8_async__tests__ASYNC220_ASYNC220.py.snap | 82 ++++++++++++ ...8_async__tests__ASYNC222_ASYNC222.py.snap} | 10 +- .../ruff_linter/src/rules/flake8_trio/mod.rs | 12 +- ...ke8_trio__tests__ASYNC100_ASYNC100.py.snap | 20 +++ ...e8_trio__tests__ASYNC105_ASYNC105.py.snap} | 120 +++++++++--------- ...ke8_trio__tests__ASYNC109_ASYNC109.py.snap | 16 +++ ...ke8_trio__tests__ASYNC110_ASYNC110.py.snap | 20 +++ ...e8_trio__tests__ASYNC115_ASYNC115.py.snap} | 64 +++++----- ...lake8_trio__tests__TRIO100_TRIO100.py.snap | 22 ---- ...lake8_trio__tests__TRIO109_TRIO109.py.snap | 18 --- ...lake8_trio__tests__TRIO110_TRIO110.py.snap | 22 ---- ruff.schema.json | 21 ++- 27 files changed, 351 insertions(+), 371 deletions(-) rename crates/ruff_linter/resources/test/fixtures/{flake8_trio/TRIO105.py => flake8_async/ASYNC105.py} (95%) rename crates/ruff_linter/resources/test/fixtures/{flake8_trio/TRIO109.py => flake8_async/ASYNC109.py} (100%) rename crates/ruff_linter/resources/test/fixtures/{flake8_trio/TRIO110.py => flake8_async/ASYNC110.py} (100%) rename crates/ruff_linter/resources/test/fixtures/{flake8_trio/TRIO115.py => flake8_async/ASYNC115.py} (85%) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py rename crates/ruff_linter/resources/test/fixtures/flake8_async/{ASYNC101.py => ASYNC220.py} (84%) rename crates/ruff_linter/resources/test/fixtures/flake8_async/{ASYNC102.py => ASYNC222.py} (100%) delete mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py delete mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap delete mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC101_ASYNC101.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap rename crates/ruff_linter/src/rules/flake8_async/snapshots/{ruff_linter__rules__flake8_async__tests__ASYNC102_ASYNC102.py.snap => ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap} (52%) create mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap rename crates/ruff_linter/src/rules/flake8_trio/snapshots/{ruff_linter__rules__flake8_trio__tests__TRIO105_TRIO105.py.snap => ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap} (80%) create mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap rename crates/ruff_linter/src/rules/flake8_trio/snapshots/{ruff_linter__rules__flake8_trio__tests__TRIO115_TRIO115.py.snap => ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap} (54%) delete mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO100_TRIO100.py.snap delete mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO109_TRIO109.py.snap delete mode 100644 crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO110_TRIO110.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py index 532273a7b4676..4499657cc2698 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py @@ -1,23 +1,27 @@ -import urllib.request -import requests -import httpx +import trio -async def foo(): - urllib.request.urlopen("http://example.com/foo/bar").read() +async def func(): + with trio.fail_after(): + ... -async def foo(): - requests.get() +async def func(): + with trio.fail_at(): + await ... -async def foo(): - httpx.get() +async def func(): + with trio.move_on_after(): + ... -async def foo(): - requests.post() +async def func(): + with trio.move_at(): + await ... -async def foo(): - httpx.post() +async def func(): + with trio.move_at(): + async with trio.open_nursery() as nursery: + ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC105.py similarity index 95% rename from crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC105.py index 4668d114c9a26..69dafa01e6f9c 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC105.py @@ -26,7 +26,7 @@ async def func() -> None: await trio.lowlevel.wait_task_rescheduled(foo) await trio.lowlevel.wait_writable(foo) - # TRIO105 + # ASYNC105 trio.aclose_forcefully(foo) trio.open_file(foo) trio.open_ssl_over_tcp_listeners(foo, foo) @@ -55,10 +55,10 @@ async def func() -> None: async with await trio.open_file(foo): # Ok pass - async with trio.open_file(foo): # TRIO105 + async with trio.open_file(foo): # ASYNC105 pass def func() -> None: - # TRIO105 (without fix) + # ASYNC105 (without fix) trio.open_file(foo) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC109.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC109.py diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC110.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC110.py diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py similarity index 85% rename from crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py index bd89567dc10c2..fd4f42d156e60 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py @@ -2,19 +2,19 @@ async def func(): import trio from trio import sleep - await trio.sleep(0) # TRIO115 + await trio.sleep(0) # ASYNC115 await trio.sleep(1) # OK await trio.sleep(0, 1) # OK await trio.sleep(...) # OK await trio.sleep() # OK - trio.sleep(0) # TRIO115 + trio.sleep(0) # ASYNC115 foo = 0 trio.sleep(foo) # OK trio.sleep(1) # OK time.sleep(0) # OK - sleep(0) # TRIO115 + sleep(0) # ASYNC115 bar = "bar" trio.sleep(bar) @@ -45,18 +45,18 @@ async def func(): def func(): import trio - trio.run(trio.sleep(0)) # TRIO115 + trio.run(trio.sleep(0)) # ASYNC115 from trio import Event, sleep def func(): - sleep(0) # TRIO115 + sleep(0) # ASYNC115 async def func(): - await sleep(seconds=0) # TRIO115 + await sleep(seconds=0) # ASYNC115 def func(): diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py new file mode 100644 index 0000000000000..532273a7b4676 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py @@ -0,0 +1,23 @@ +import urllib.request +import requests +import httpx + + +async def foo(): + urllib.request.urlopen("http://example.com/foo/bar").read() + + +async def foo(): + requests.get() + + +async def foo(): + httpx.get() + + +async def foo(): + requests.post() + + +async def foo(): + httpx.post() diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC101.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py similarity index 84% rename from crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC101.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py index 4c4f78bd452a7..be070ccb48d6f 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC101.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py @@ -38,16 +38,16 @@ async def func(): async def func(): - Path("foo").open() # ASYNC101 + Path("foo").open() # ASYNC220 async def func(): p = Path("foo") - p.open() # ASYNC101 + p.open() # ASYNC220 async def func(): - with Path("foo").open() as f: # ASYNC101 + with Path("foo").open() as f: # ASYNC220 pass @@ -55,13 +55,13 @@ async def func() -> None: p = Path("foo") async def bar(): - p.open() # ASYNC101 + p.open() # ASYNC220 async def func() -> None: (p1, p2) = (Path("foo"), Path("bar")) - p1.open() # ASYNC101 + p1.open() # ASYNC220 # Non-violation cases for pathlib: diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC102.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC102.py rename to crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py b/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py deleted file mode 100644 index 4499657cc2698..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO100.py +++ /dev/null @@ -1,27 +0,0 @@ -import trio - - -async def func(): - with trio.fail_after(): - ... - - -async def func(): - with trio.fail_at(): - await ... - - -async def func(): - with trio.move_on_after(): - ... - - -async def func(): - with trio.move_at(): - await ... - - -async def func(): - with trio.move_at(): - async with trio.open_nursery() as nursery: - ... diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 3274a4cdb5faf..f1da6af450c68 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -292,17 +292,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax), // flake8-async - (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), - (Flake8Async, "101") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction), - (Flake8Async, "102") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction), + (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioTimeoutWithoutAwait), + (Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioSyncCall), + (Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout), + (Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioUnneededSleep), + (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioZeroSleepCall), (Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall), - - // flake8-trio - (Flake8Trio, "100") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioTimeoutWithoutAwait), - (Flake8Trio, "105") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioSyncCall), - (Flake8Trio, "109") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout), - (Flake8Trio, "110") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioUnneededSleep), - (Flake8Trio, "115") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioZeroSleepCall), + (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), + (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction), + (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction), // flake8-builtins (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), diff --git a/crates/ruff_linter/src/registry.rs b/crates/ruff_linter/src/registry.rs index fa6bdee4587db..6cb5b39c922fb 100644 --- a/crates/ruff_linter/src/registry.rs +++ b/crates/ruff_linter/src/registry.rs @@ -64,9 +64,6 @@ pub enum Linter { /// [flake8-async](https://pypi.org/project/flake8-async/) #[prefix = "ASYNC"] Flake8Async, - /// [flake8-trio](https://pypi.org/project/flake8-trio/) - #[prefix = "TRIO"] - Flake8Trio, /// [flake8-bandit](https://pypi.org/project/flake8-bandit/) #[prefix = "S"] Flake8Bandit, diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index dfbf3dab1f828..a2ebe1bcca908 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -13,9 +13,9 @@ mod tests { use crate::settings::LinterSettings; use crate::test::test_path; - #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC100.py"))] - #[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC101.py"))] - #[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC102.py"))] + #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] + #[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC220.py"))] + #[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC222.py"))] #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap deleted file mode 100644 index b7612ca1bc6ab..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap +++ /dev/null @@ -1,39 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_async/mod.rs ---- -ASYNC100.py:7:5: ASYNC100 Async functions should not call blocking HTTP methods - | -6 | async def foo(): -7 | urllib.request.urlopen("http://example.com/foo/bar").read() - | ^^^^^^^^^^^^^^^^^^^^^^ ASYNC100 - | - -ASYNC100.py:11:5: ASYNC100 Async functions should not call blocking HTTP methods - | -10 | async def foo(): -11 | requests.get() - | ^^^^^^^^^^^^ ASYNC100 - | - -ASYNC100.py:15:5: ASYNC100 Async functions should not call blocking HTTP methods - | -14 | async def foo(): -15 | httpx.get() - | ^^^^^^^^^ ASYNC100 - | - -ASYNC100.py:19:5: ASYNC100 Async functions should not call blocking HTTP methods - | -18 | async def foo(): -19 | requests.post() - | ^^^^^^^^^^^^^ ASYNC100 - | - -ASYNC100.py:23:5: ASYNC100 Async functions should not call blocking HTTP methods - | -22 | async def foo(): -23 | httpx.post() - | ^^^^^^^^^^ ASYNC100 - | - - diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC101_ASYNC101.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC101_ASYNC101.py.snap deleted file mode 100644 index 969e9ec1f48f2..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC101_ASYNC101.py.snap +++ /dev/null @@ -1,84 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_async/mod.rs ---- -ASYNC101.py:10:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | - 9 | async def func(): -10 | open("foo") - | ^^^^ ASYNC101 - | - -ASYNC101.py:14:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -13 | async def func(): -14 | time.sleep(1) - | ^^^^^^^^^^ ASYNC101 - | - -ASYNC101.py:18:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -17 | async def func(): -18 | subprocess.run("foo") - | ^^^^^^^^^^^^^^ ASYNC101 - | - -ASYNC101.py:22:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -21 | async def func(): -22 | subprocess.call("foo") - | ^^^^^^^^^^^^^^^ ASYNC101 - | - -ASYNC101.py:30:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -29 | async def func(): -30 | os.wait4(10) - | ^^^^^^^^ ASYNC101 - | - -ASYNC101.py:34:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -33 | async def func(): -34 | os.wait(12) - | ^^^^^^^ ASYNC101 - | - -ASYNC101.py:41:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -40 | async def func(): -41 | Path("foo").open() # ASYNC101 - | ^^^^^^^^^^^^^^^^ ASYNC101 - | - -ASYNC101.py:46:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -44 | async def func(): -45 | p = Path("foo") -46 | p.open() # ASYNC101 - | ^^^^^^ ASYNC101 - | - -ASYNC101.py:50:10: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -49 | async def func(): -50 | with Path("foo").open() as f: # ASYNC101 - | ^^^^^^^^^^^^^^^^ ASYNC101 -51 | pass - | - -ASYNC101.py:58:9: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -57 | async def bar(): -58 | p.open() # ASYNC101 - | ^^^^^^ ASYNC101 - | - -ASYNC101.py:64:5: ASYNC101 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -62 | (p1, p2) = (Path("foo"), Path("bar")) -63 | -64 | p1.open() # ASYNC101 - | ^^^^^^^ ASYNC101 - | - - diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap new file mode 100644 index 0000000000000..6807e8db02745 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap @@ -0,0 +1,37 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC210.py:7:5: ASYNC210 Async functions should not call blocking HTTP methods + | +6 | async def foo(): +7 | urllib.request.urlopen("http://example.com/foo/bar").read() + | ^^^^^^^^^^^^^^^^^^^^^^ ASYNC210 + | + +ASYNC210.py:11:5: ASYNC210 Async functions should not call blocking HTTP methods + | +10 | async def foo(): +11 | requests.get() + | ^^^^^^^^^^^^ ASYNC210 + | + +ASYNC210.py:15:5: ASYNC210 Async functions should not call blocking HTTP methods + | +14 | async def foo(): +15 | httpx.get() + | ^^^^^^^^^ ASYNC210 + | + +ASYNC210.py:19:5: ASYNC210 Async functions should not call blocking HTTP methods + | +18 | async def foo(): +19 | requests.post() + | ^^^^^^^^^^^^^ ASYNC210 + | + +ASYNC210.py:23:5: ASYNC210 Async functions should not call blocking HTTP methods + | +22 | async def foo(): +23 | httpx.post() + | ^^^^^^^^^^ ASYNC210 + | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap new file mode 100644 index 0000000000000..745f042c6984c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap @@ -0,0 +1,82 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC220.py:10:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | + 9 | async def func(): +10 | open("foo") + | ^^^^ ASYNC220 + | + +ASYNC220.py:14:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +13 | async def func(): +14 | time.sleep(1) + | ^^^^^^^^^^ ASYNC220 + | + +ASYNC220.py:18:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +17 | async def func(): +18 | subprocess.run("foo") + | ^^^^^^^^^^^^^^ ASYNC220 + | + +ASYNC220.py:22:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +21 | async def func(): +22 | subprocess.call("foo") + | ^^^^^^^^^^^^^^^ ASYNC220 + | + +ASYNC220.py:30:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +29 | async def func(): +30 | os.wait4(10) + | ^^^^^^^^ ASYNC220 + | + +ASYNC220.py:34:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +33 | async def func(): +34 | os.wait(12) + | ^^^^^^^ ASYNC220 + | + +ASYNC220.py:41:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +40 | async def func(): +41 | Path("foo").open() # ASYNC220 + | ^^^^^^^^^^^^^^^^ ASYNC220 + | + +ASYNC220.py:46:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +44 | async def func(): +45 | p = Path("foo") +46 | p.open() # ASYNC220 + | ^^^^^^ ASYNC220 + | + +ASYNC220.py:50:10: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +49 | async def func(): +50 | with Path("foo").open() as f: # ASYNC220 + | ^^^^^^^^^^^^^^^^ ASYNC220 +51 | pass + | + +ASYNC220.py:58:9: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +57 | async def bar(): +58 | p.open() # ASYNC220 + | ^^^^^^ ASYNC220 + | + +ASYNC220.py:64:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods + | +62 | (p1, p2) = (Path("foo"), Path("bar")) +63 | +64 | p1.open() # ASYNC220 + | ^^^^^^^ ASYNC220 + | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC102_ASYNC102.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap similarity index 52% rename from crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC102_ASYNC102.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap index d97b6da81cfbb..a3e23b483f628 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC102_ASYNC102.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap @@ -1,18 +1,16 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC102.py:5:5: ASYNC102 Async functions should not call synchronous `os` methods +ASYNC222.py:5:5: ASYNC222 Async functions should not call synchronous `os` methods | 4 | async def foo(): 5 | os.popen() - | ^^^^^^^^ ASYNC102 + | ^^^^^^^^ ASYNC222 | -ASYNC102.py:9:5: ASYNC102 Async functions should not call synchronous `os` methods +ASYNC222.py:9:5: ASYNC222 Async functions should not call synchronous `os` methods | 8 | async def foo(): 9 | os.spawnl() - | ^^^^^^^^^ ASYNC102 + | ^^^^^^^^^ ASYNC222 | - - diff --git a/crates/ruff_linter/src/rules/flake8_trio/mod.rs b/crates/ruff_linter/src/rules/flake8_trio/mod.rs index 2e0bb7911ecba..995359660cf02 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/mod.rs @@ -14,15 +14,15 @@ mod tests { use crate::settings::LinterSettings; use crate::test::test_path; - #[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))] - #[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))] - #[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("TRIO109.py"))] - #[test_case(Rule::TrioUnneededSleep, Path::new("TRIO110.py"))] - #[test_case(Rule::TrioZeroSleepCall, Path::new("TRIO115.py"))] + #[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))] + #[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))] + #[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))] + #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] + #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( - Path::new("flake8_trio").join(path).as_path(), + Path::new("flake8_async").join(path).as_path(), &LinterSettings::for_rule(rule_code), )?; assert_messages!(snapshot, diagnostics); diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap new file mode 100644 index 0000000000000..39514476b5e09 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +--- +ASYNC100.py:5:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +4 | async def func(): +5 | with trio.fail_after(): + | _____^ +6 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:15:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +14 | async def func(): +15 | with trio.move_on_after(): + | _____^ +16 | | ... + | |___________^ ASYNC100 + | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO105_TRIO105.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap similarity index 80% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO105_TRIO105.py.snap rename to crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap index 67312f78a440b..726bda2eca1eb 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO105_TRIO105.py.snap +++ b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap @@ -1,11 +1,11 @@ --- source: crates/ruff_linter/src/rules/flake8_trio/mod.rs --- -TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited +ASYNC105.py:30:5: ASYNC105 [*] Call to `trio.aclose_forcefully` is not immediately awaited | -29 | # TRIO105 +29 | # ASYNC105 30 | trio.aclose_forcefully(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 31 | trio.open_file(foo) 32 | trio.open_ssl_over_tcp_listeners(foo, foo) | @@ -14,19 +14,19 @@ TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately ℹ Unsafe fix 27 27 | await trio.lowlevel.wait_writable(foo) 28 28 | -29 29 | # TRIO105 +29 29 | # ASYNC105 30 |- trio.aclose_forcefully(foo) 30 |+ await trio.aclose_forcefully(foo) 31 31 | trio.open_file(foo) 32 32 | trio.open_ssl_over_tcp_listeners(foo, foo) 33 33 | trio.open_ssl_over_tcp_stream(foo, foo) -TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited +ASYNC105.py:31:5: ASYNC105 [*] Call to `trio.open_file` is not immediately awaited | -29 | # TRIO105 +29 | # ASYNC105 30 | trio.aclose_forcefully(foo) 31 | trio.open_file(foo) - | ^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^ ASYNC105 32 | trio.open_ssl_over_tcp_listeners(foo, foo) 33 | trio.open_ssl_over_tcp_stream(foo, foo) | @@ -34,7 +34,7 @@ TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited ℹ Unsafe fix 28 28 | -29 29 | # TRIO105 +29 29 | # ASYNC105 30 30 | trio.aclose_forcefully(foo) 31 |- trio.open_file(foo) 31 |+ await trio.open_file(foo) @@ -42,19 +42,19 @@ TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited 33 33 | trio.open_ssl_over_tcp_stream(foo, foo) 34 34 | trio.open_tcp_listeners(foo) -TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited +ASYNC105.py:32:5: ASYNC105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited | 30 | trio.aclose_forcefully(foo) 31 | trio.open_file(foo) 32 | trio.open_ssl_over_tcp_listeners(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 33 | trio.open_ssl_over_tcp_stream(foo, foo) 34 | trio.open_tcp_listeners(foo) | = help: Add `await` ℹ Unsafe fix -29 29 | # TRIO105 +29 29 | # ASYNC105 30 30 | trio.aclose_forcefully(foo) 31 31 | trio.open_file(foo) 32 |- trio.open_ssl_over_tcp_listeners(foo, foo) @@ -63,12 +63,12 @@ TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not i 34 34 | trio.open_tcp_listeners(foo) 35 35 | trio.open_tcp_stream(foo, foo) -TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited +ASYNC105.py:33:5: ASYNC105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited | 31 | trio.open_file(foo) 32 | trio.open_ssl_over_tcp_listeners(foo, foo) 33 | trio.open_ssl_over_tcp_stream(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 34 | trio.open_tcp_listeners(foo) 35 | trio.open_tcp_stream(foo, foo) | @@ -84,12 +84,12 @@ TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not imme 35 35 | trio.open_tcp_stream(foo, foo) 36 36 | trio.open_unix_socket(foo) -TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited +ASYNC105.py:34:5: ASYNC105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited | 32 | trio.open_ssl_over_tcp_listeners(foo, foo) 33 | trio.open_ssl_over_tcp_stream(foo, foo) 34 | trio.open_tcp_listeners(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 35 | trio.open_tcp_stream(foo, foo) 36 | trio.open_unix_socket(foo) | @@ -105,12 +105,12 @@ TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediatel 36 36 | trio.open_unix_socket(foo) 37 37 | trio.run_process(foo) -TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately awaited +ASYNC105.py:35:5: ASYNC105 [*] Call to `trio.open_tcp_stream` is not immediately awaited | 33 | trio.open_ssl_over_tcp_stream(foo, foo) 34 | trio.open_tcp_listeners(foo) 35 | trio.open_tcp_stream(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 36 | trio.open_unix_socket(foo) 37 | trio.run_process(foo) | @@ -126,12 +126,12 @@ TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately a 37 37 | trio.run_process(foo) 38 38 | trio.serve_listeners(foo, foo) -TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately awaited +ASYNC105.py:36:5: ASYNC105 [*] Call to `trio.open_unix_socket` is not immediately awaited | 34 | trio.open_tcp_listeners(foo) 35 | trio.open_tcp_stream(foo, foo) 36 | trio.open_unix_socket(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 37 | trio.run_process(foo) 38 | trio.serve_listeners(foo, foo) | @@ -147,12 +147,12 @@ TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately 38 38 | trio.serve_listeners(foo, foo) 39 39 | trio.serve_ssl_over_tcp(foo, foo, foo) -TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately awaited +ASYNC105.py:37:5: ASYNC105 [*] Call to `trio.run_process` is not immediately awaited | 35 | trio.open_tcp_stream(foo, foo) 36 | trio.open_unix_socket(foo) 37 | trio.run_process(foo) - | ^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^ ASYNC105 38 | trio.serve_listeners(foo, foo) 39 | trio.serve_ssl_over_tcp(foo, foo, foo) | @@ -168,12 +168,12 @@ TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately await 39 39 | trio.serve_ssl_over_tcp(foo, foo, foo) 40 40 | trio.serve_tcp(foo, foo) -TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately awaited +ASYNC105.py:38:5: ASYNC105 [*] Call to `trio.serve_listeners` is not immediately awaited | 36 | trio.open_unix_socket(foo) 37 | trio.run_process(foo) 38 | trio.serve_listeners(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 39 | trio.serve_ssl_over_tcp(foo, foo, foo) 40 | trio.serve_tcp(foo, foo) | @@ -189,12 +189,12 @@ TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately a 40 40 | trio.serve_tcp(foo, foo) 41 41 | trio.sleep(foo) -TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited +ASYNC105.py:39:5: ASYNC105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited | 37 | trio.run_process(foo) 38 | trio.serve_listeners(foo, foo) 39 | trio.serve_ssl_over_tcp(foo, foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 40 | trio.serve_tcp(foo, foo) 41 | trio.sleep(foo) | @@ -210,12 +210,12 @@ TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediatel 41 41 | trio.sleep(foo) 42 42 | trio.sleep_forever() -TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited +ASYNC105.py:40:5: ASYNC105 [*] Call to `trio.serve_tcp` is not immediately awaited | 38 | trio.serve_listeners(foo, foo) 39 | trio.serve_ssl_over_tcp(foo, foo, foo) 40 | trio.serve_tcp(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 41 | trio.sleep(foo) 42 | trio.sleep_forever() | @@ -231,12 +231,12 @@ TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited 42 42 | trio.sleep_forever() 43 43 | trio.sleep_until(foo) -TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited +ASYNC105.py:41:5: ASYNC105 [*] Call to `trio.sleep` is not immediately awaited | 39 | trio.serve_ssl_over_tcp(foo, foo, foo) 40 | trio.serve_tcp(foo, foo) 41 | trio.sleep(foo) - | ^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^ ASYNC105 42 | trio.sleep_forever() 43 | trio.sleep_until(foo) | @@ -252,12 +252,12 @@ TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited 43 43 | trio.sleep_until(foo) 44 44 | trio.lowlevel.cancel_shielded_checkpoint() -TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awaited +ASYNC105.py:42:5: ASYNC105 [*] Call to `trio.sleep_forever` is not immediately awaited | 40 | trio.serve_tcp(foo, foo) 41 | trio.sleep(foo) 42 | trio.sleep_forever() - | ^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^ ASYNC105 43 | trio.sleep_until(foo) 44 | trio.lowlevel.cancel_shielded_checkpoint() | @@ -273,12 +273,12 @@ TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awa 44 44 | trio.lowlevel.cancel_shielded_checkpoint() 45 45 | trio.lowlevel.checkpoint() -TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited +ASYNC105.py:44:5: ASYNC105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited | 42 | trio.sleep_forever() 43 | trio.sleep_until(foo) 44 | trio.lowlevel.cancel_shielded_checkpoint() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 45 | trio.lowlevel.checkpoint() 46 | trio.lowlevel.checkpoint_if_cancelled() | @@ -294,12 +294,12 @@ TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` 46 46 | trio.lowlevel.checkpoint_if_cancelled() 47 47 | trio.lowlevel.open_process() -TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited +ASYNC105.py:45:5: ASYNC105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited | 43 | trio.sleep_until(foo) 44 | trio.lowlevel.cancel_shielded_checkpoint() 45 | trio.lowlevel.checkpoint() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 46 | trio.lowlevel.checkpoint_if_cancelled() 47 | trio.lowlevel.open_process() | @@ -315,12 +315,12 @@ TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediate 47 47 | trio.lowlevel.open_process() 48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) -TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited +ASYNC105.py:46:5: ASYNC105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited | 44 | trio.lowlevel.cancel_shielded_checkpoint() 45 | trio.lowlevel.checkpoint() 46 | trio.lowlevel.checkpoint_if_cancelled() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 47 | trio.lowlevel.open_process() 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) | @@ -336,12 +336,12 @@ TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is 48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) 49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) -TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited +ASYNC105.py:47:5: ASYNC105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited | 45 | trio.lowlevel.checkpoint() 46 | trio.lowlevel.checkpoint_if_cancelled() 47 | trio.lowlevel.open_process() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) | @@ -357,12 +357,12 @@ TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immedia 49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) 50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) -TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited +ASYNC105.py:48:5: ASYNC105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited | 46 | trio.lowlevel.checkpoint_if_cancelled() 47 | trio.lowlevel.open_process() 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) | @@ -378,12 +378,12 @@ TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine 50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) 51 51 | trio.lowlevel.wait_readable(foo) -TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited +ASYNC105.py:49:5: ASYNC105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited | 47 | trio.lowlevel.open_process() 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) 51 | trio.lowlevel.wait_readable(foo) | @@ -399,12 +399,12 @@ TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_ 51 51 | trio.lowlevel.wait_readable(foo) 52 52 | trio.lowlevel.wait_task_rescheduled(foo) -TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited +ASYNC105.py:50:5: ASYNC105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited | 48 | trio.lowlevel.permanently_detach_coroutine_object(foo) 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 51 | trio.lowlevel.wait_readable(foo) 52 | trio.lowlevel.wait_task_rescheduled(foo) | @@ -420,12 +420,12 @@ TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine 52 52 | trio.lowlevel.wait_task_rescheduled(foo) 53 53 | trio.lowlevel.wait_writable(foo) -TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited +ASYNC105.py:51:5: ASYNC105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited | 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo) 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) 51 | trio.lowlevel.wait_readable(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 52 | trio.lowlevel.wait_task_rescheduled(foo) 53 | trio.lowlevel.wait_writable(foo) | @@ -441,12 +441,12 @@ TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immedi 53 53 | trio.lowlevel.wait_writable(foo) 54 54 | -TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited +ASYNC105.py:52:5: ASYNC105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited | 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo) 51 | trio.lowlevel.wait_readable(foo) 52 | trio.lowlevel.wait_task_rescheduled(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 53 | trio.lowlevel.wait_writable(foo) | = help: Add `await` @@ -461,12 +461,12 @@ TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is no 54 54 | 55 55 | async with await trio.open_file(foo): # Ok -TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited +ASYNC105.py:53:5: ASYNC105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited | 51 | trio.lowlevel.wait_readable(foo) 52 | trio.lowlevel.wait_task_rescheduled(foo) 53 | trio.lowlevel.wait_writable(foo) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC105 54 | 55 | async with await trio.open_file(foo): # Ok | @@ -482,12 +482,12 @@ TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immedi 55 55 | async with await trio.open_file(foo): # Ok 56 56 | pass -TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaited +ASYNC105.py:58:16: ASYNC105 [*] Call to `trio.open_file` is not immediately awaited | 56 | pass 57 | -58 | async with trio.open_file(foo): # TRIO105 - | ^^^^^^^^^^^^^^^^^^^ TRIO105 +58 | async with trio.open_file(foo): # ASYNC105 + | ^^^^^^^^^^^^^^^^^^^ ASYNC105 59 | pass | = help: Add `await` @@ -496,19 +496,17 @@ TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaite 55 55 | async with await trio.open_file(foo): # Ok 56 56 | pass 57 57 | -58 |- async with trio.open_file(foo): # TRIO105 - 58 |+ async with await trio.open_file(foo): # TRIO105 +58 |- async with trio.open_file(foo): # ASYNC105 + 58 |+ async with await trio.open_file(foo): # ASYNC105 59 59 | pass 60 60 | 61 61 | -TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited +ASYNC105.py:64:5: ASYNC105 Call to `trio.open_file` is not immediately awaited | 62 | def func() -> None: -63 | # TRIO105 (without fix) +63 | # ASYNC105 (without fix) 64 | trio.open_file(foo) - | ^^^^^^^^^^^^^^^^^^^ TRIO105 + | ^^^^^^^^^^^^^^^^^^^ ASYNC105 | = help: Add `await` - - diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap new file mode 100644 index 0000000000000..0131a275f5ea4 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +--- +ASYNC109.py:8:16: ASYNC109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior + | +8 | async def func(timeout): + | ^^^^^^^ ASYNC109 +9 | ... + | + +ASYNC109.py:12:16: ASYNC109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior + | +12 | async def func(timeout=10): + | ^^^^^^^^^^ ASYNC109 +13 | ... + | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap new file mode 100644 index 0000000000000..2604c633e7e3c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +--- +ASYNC110.py:5:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop + | +4 | async def func(): +5 | while True: + | _____^ +6 | | await trio.sleep(10) + | |____________________________^ ASYNC110 + | + +ASYNC110.py:10:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop + | + 9 | async def func(): +10 | while True: + | _____^ +11 | | await trio.sleep_until(10) + | |__________________________________^ ASYNC110 + | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO115_TRIO115.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap similarity index 54% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO115_TRIO115.py.snap rename to crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap index 3de63ea470e29..b020ebb031ec4 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO115_TRIO115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap @@ -1,12 +1,12 @@ --- source: crates/ruff_linter/src/rules/flake8_trio/mod.rs --- -TRIO115.py:5:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:5:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 3 | from trio import sleep 4 | -5 | await trio.sleep(0) # TRIO115 - | ^^^^^^^^^^^^^ TRIO115 +5 | await trio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 6 | await trio.sleep(1) # OK 7 | await trio.sleep(0, 1) # OK | @@ -16,18 +16,18 @@ TRIO115.py:5:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s 2 2 | import trio 3 3 | from trio import sleep 4 4 | -5 |- await trio.sleep(0) # TRIO115 - 5 |+ await trio.lowlevel.checkpoint() # TRIO115 +5 |- await trio.sleep(0) # ASYNC115 + 5 |+ await trio.lowlevel.checkpoint() # ASYNC115 6 6 | await trio.sleep(1) # OK 7 7 | await trio.sleep(0, 1) # OK 8 8 | await trio.sleep(...) # OK -TRIO115.py:11:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:11:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 9 | await trio.sleep() # OK 10 | -11 | trio.sleep(0) # TRIO115 - | ^^^^^^^^^^^^^ TRIO115 +11 | trio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 12 | foo = 0 13 | trio.sleep(foo) # OK | @@ -37,18 +37,18 @@ TRIO115.py:11:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s 8 8 | await trio.sleep(...) # OK 9 9 | await trio.sleep() # OK 10 10 | -11 |- trio.sleep(0) # TRIO115 - 11 |+ trio.lowlevel.checkpoint() # TRIO115 +11 |- trio.sleep(0) # ASYNC115 + 11 |+ trio.lowlevel.checkpoint() # ASYNC115 12 12 | foo = 0 13 13 | trio.sleep(foo) # OK 14 14 | trio.sleep(1) # OK -TRIO115.py:17:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:17:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 15 | time.sleep(0) # OK 16 | -17 | sleep(0) # TRIO115 - | ^^^^^^^^ TRIO115 +17 | sleep(0) # ASYNC115 + | ^^^^^^^^ ASYNC115 18 | 19 | bar = "bar" | @@ -58,18 +58,18 @@ TRIO115.py:17:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s 14 14 | trio.sleep(1) # OK 15 15 | time.sleep(0) # OK 16 16 | -17 |- sleep(0) # TRIO115 - 17 |+ trio.lowlevel.checkpoint() # TRIO115 +17 |- sleep(0) # ASYNC115 + 17 |+ trio.lowlevel.checkpoint() # ASYNC115 18 18 | 19 19 | bar = "bar" 20 20 | trio.sleep(bar) -TRIO115.py:48:14: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:48:14: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 46 | import trio 47 | -48 | trio.run(trio.sleep(0)) # TRIO115 - | ^^^^^^^^^^^^^ TRIO115 +48 | trio.run(trio.sleep(0)) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 | = help: Replace with `trio.lowlevel.checkpoint()` @@ -77,22 +77,22 @@ TRIO115.py:48:14: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio. 45 45 | def func(): 46 46 | import trio 47 47 | -48 |- trio.run(trio.sleep(0)) # TRIO115 - 48 |+ trio.run(trio.lowlevel.checkpoint()) # TRIO115 +48 |- trio.run(trio.sleep(0)) # ASYNC115 + 48 |+ trio.run(trio.lowlevel.checkpoint()) # ASYNC115 49 49 | 50 50 | 51 51 | from trio import Event, sleep -TRIO115.py:55:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:55:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 54 | def func(): -55 | sleep(0) # TRIO115 - | ^^^^^^^^ TRIO115 +55 | sleep(0) # ASYNC115 + | ^^^^^^^^ ASYNC115 | = help: Replace with `trio.lowlevel.checkpoint()` ℹ Safe fix -48 48 | trio.run(trio.sleep(0)) # TRIO115 +48 48 | trio.run(trio.sleep(0)) # ASYNC115 49 49 | 50 50 | 51 |-from trio import Event, sleep @@ -100,22 +100,22 @@ TRIO115.py:55:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s 52 52 | 53 53 | 54 54 | def func(): -55 |- sleep(0) # TRIO115 - 55 |+ lowlevel.checkpoint() # TRIO115 +55 |- sleep(0) # ASYNC115 + 55 |+ lowlevel.checkpoint() # ASYNC115 56 56 | 57 57 | 58 58 | async def func(): -TRIO115.py:59:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` +ASYNC115.py:59:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | 58 | async def func(): -59 | await sleep(seconds=0) # TRIO115 - | ^^^^^^^^^^^^^^^^ TRIO115 +59 | await sleep(seconds=0) # ASYNC115 + | ^^^^^^^^^^^^^^^^ ASYNC115 | = help: Replace with `trio.lowlevel.checkpoint()` ℹ Safe fix -48 48 | trio.run(trio.sleep(0)) # TRIO115 +48 48 | trio.run(trio.sleep(0)) # ASYNC115 49 49 | 50 50 | 51 |-from trio import Event, sleep @@ -127,8 +127,8 @@ TRIO115.py:59:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio. 56 56 | 57 57 | 58 58 | async def func(): -59 |- await sleep(seconds=0) # TRIO115 - 59 |+ await lowlevel.checkpoint() # TRIO115 +59 |- await sleep(seconds=0) # ASYNC115 + 59 |+ await lowlevel.checkpoint() # ASYNC115 60 60 | 61 61 | 62 62 | def func(): diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO100_TRIO100.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO100_TRIO100.py.snap deleted file mode 100644 index 1d364b2670695..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO100_TRIO100.py.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs ---- -TRIO100.py:5:5: TRIO100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. - | -4 | async def func(): -5 | with trio.fail_after(): - | _____^ -6 | | ... - | |___________^ TRIO100 - | - -TRIO100.py:15:5: TRIO100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. - | -14 | async def func(): -15 | with trio.move_on_after(): - | _____^ -16 | | ... - | |___________^ TRIO100 - | - - diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO109_TRIO109.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO109_TRIO109.py.snap deleted file mode 100644 index fc08800b672b2..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO109_TRIO109.py.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs ---- -TRIO109.py:8:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior - | -8 | async def func(timeout): - | ^^^^^^^ TRIO109 -9 | ... - | - -TRIO109.py:12:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior - | -12 | async def func(timeout=10): - | ^^^^^^^^^^ TRIO109 -13 | ... - | - - diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO110_TRIO110.py.snap b/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO110_TRIO110.py.snap deleted file mode 100644 index d95be0a65d321..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO110_TRIO110.py.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs ---- -TRIO110.py:5:5: TRIO110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop - | -4 | async def func(): -5 | while True: - | _____^ -6 | | await trio.sleep(10) - | |____________________________^ TRIO110 - | - -TRIO110.py:10:5: TRIO110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop - | - 9 | async def func(): -10 | while True: - | _____^ -11 | | await trio.sleep_until(10) - | |__________________________________^ TRIO110 - | - - diff --git a/ruff.schema.json b/ruff.schema.json index 6a8d7e580719d..4c34911384432 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2693,10 +2693,18 @@ "ASYNC1", "ASYNC10", "ASYNC100", - "ASYNC101", - "ASYNC102", + "ASYNC105", + "ASYNC109", "ASYNC11", + "ASYNC110", + "ASYNC115", "ASYNC116", + "ASYNC2", + "ASYNC21", + "ASYNC210", + "ASYNC22", + "ASYNC220", + "ASYNC222", "B", "B0", "B00", @@ -3837,15 +3845,6 @@ "TID251", "TID252", "TID253", - "TRIO", - "TRIO1", - "TRIO10", - "TRIO100", - "TRIO105", - "TRIO109", - "TRIO11", - "TRIO110", - "TRIO115", "TRY", "TRY0", "TRY00", From e2765c677c75c4c0e6753ee3415701e31ccee426 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 20:48:10 -0400 Subject: [PATCH 02/22] merge packages --- .../src/checkers/ast/analyze/expression.rs | 6 ++-- .../src/checkers/ast/analyze/statement.rs | 8 ++--- crates/ruff_linter/src/codes.rs | 10 +++--- .../helpers.rs} | 0 .../ruff_linter/src/rules/flake8_async/mod.rs | 6 ++++ .../rules/async_function_with_timeout.rs | 0 .../src/rules/flake8_async/rules/mod.rs | 12 +++++++ .../rules/sync_call.rs | 2 +- .../rules/timeout_without_await.rs | 2 +- .../rules/unneeded_sleep.rs | 0 .../rules/zero_sleep_call.rs | 0 ...8_async__tests__ASYNC100_ASYNC100.py.snap} | 2 +- ...8_async__tests__ASYNC105_ASYNC105.py.snap} | 2 +- ...8_async__tests__ASYNC109_ASYNC109.py.snap} | 2 +- ...8_async__tests__ASYNC110_ASYNC110.py.snap} | 2 +- ...8_async__tests__ASYNC115_ASYNC115.py.snap} | 2 +- .../ruff_linter/src/rules/flake8_trio/mod.rs | 31 ------------------- .../src/rules/flake8_trio/rules/mod.rs | 11 ------- crates/ruff_linter/src/rules/mod.rs | 1 - 19 files changed, 37 insertions(+), 62 deletions(-) rename crates/ruff_linter/src/rules/{flake8_trio/method_name.rs => flake8_async/helpers.rs} (100%) rename crates/ruff_linter/src/rules/{flake8_trio => flake8_async}/rules/async_function_with_timeout.rs (100%) rename crates/ruff_linter/src/rules/{flake8_trio => flake8_async}/rules/sync_call.rs (97%) rename crates/ruff_linter/src/rules/{flake8_trio => flake8_async}/rules/timeout_without_await.rs (97%) rename crates/ruff_linter/src/rules/{flake8_trio => flake8_async}/rules/unneeded_sleep.rs (100%) rename crates/ruff_linter/src/rules/{flake8_trio => flake8_async}/rules/zero_sleep_call.rs (100%) rename crates/ruff_linter/src/rules/{flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap => flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap} (91%) rename crates/ruff_linter/src/rules/{flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap => flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC105_ASYNC105.py.snap} (99%) rename crates/ruff_linter/src/rules/{flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap => flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC109_ASYNC109.py.snap} (88%) rename crates/ruff_linter/src/rules/{flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap => flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC110_ASYNC110.py.snap} (89%) rename crates/ruff_linter/src/rules/{flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap => flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap} (98%) delete mode 100644 crates/ruff_linter/src/rules/flake8_trio/mod.rs delete mode 100644 crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 3b23e7cae9188..f21fe84b653bd 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -15,7 +15,7 @@ use crate::rules::{ flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django, flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging, flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, - flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_type_checking, flake8_use_pathlib, + flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff, }; use crate::settings::types::PythonVersion; @@ -963,10 +963,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { refurb::rules::no_implicit_cwd(checker, call); } if checker.enabled(Rule::TrioSyncCall) { - flake8_trio::rules::sync_call(checker, call); + flake8_async::rules::sync_call(checker, call); } if checker.enabled(Rule::TrioZeroSleepCall) { - flake8_trio::rules::zero_sleep_call(checker, call); + flake8_async::rules::zero_sleep_call(checker, call); } if checker.enabled(Rule::UnnecessaryDunderCall) { pylint::rules::unnecessary_dunder_call(checker, call); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index fdb27a664ccf7..cb2035a03b921 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -11,7 +11,7 @@ use crate::rules::{ airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger, flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots, - flake8_tidy_imports, flake8_trio, flake8_type_checking, mccabe, pandas_vet, pep8_naming, + flake8_tidy_imports, flake8_async, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops, }; use crate::settings::types::PythonVersion; @@ -357,7 +357,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) { - flake8_trio::rules::async_function_with_timeout(checker, function_def); + flake8_async::rules::async_function_with_timeout(checker, function_def); } if checker.enabled(Rule::ReimplementedOperator) { refurb::rules::reimplemented_operator(checker, &function_def.into()); @@ -1303,7 +1303,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::useless_with_lock(checker, with_stmt); } if checker.enabled(Rule::TrioTimeoutWithoutAwait) { - flake8_trio::rules::timeout_without_await(checker, with_stmt, items); + flake8_async::rules::timeout_without_await(checker, with_stmt, items); } } Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => { @@ -1320,7 +1320,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { perflint::rules::try_except_in_loop(checker, body); } if checker.enabled(Rule::TrioUnneededSleep) { - flake8_trio::rules::unneeded_sleep(checker, while_stmt); + flake8_async::rules::unneeded_sleep(checker, while_stmt); } } Stmt::For( diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index f1da6af450c68..718f0aca8cd01 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -292,11 +292,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax), // flake8-async - (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioTimeoutWithoutAwait), - (Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioSyncCall), - (Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout), - (Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioUnneededSleep), - (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_trio::rules::TrioZeroSleepCall), + (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::TrioTimeoutWithoutAwait), + (Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_async::rules::TrioSyncCall), + (Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::TrioAsyncFunctionWithTimeout), + (Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::TrioUnneededSleep), + (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall), (Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall), (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction), diff --git a/crates/ruff_linter/src/rules/flake8_trio/method_name.rs b/crates/ruff_linter/src/rules/flake8_async/helpers.rs similarity index 100% rename from crates/ruff_linter/src/rules/flake8_trio/method_name.rs rename to crates/ruff_linter/src/rules/flake8_async/helpers.rs diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index a2ebe1bcca908..3664a7b31d813 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -1,4 +1,5 @@ //! Rules from [flake8-async](https://pypi.org/project/flake8-async/). +mod helpers; pub(crate) mod rules; #[cfg(test)] @@ -13,6 +14,11 @@ mod tests { use crate::settings::LinterSettings; use crate::test::test_path; + #[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))] + #[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))] + #[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))] + #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] + #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] #[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC220.py"))] #[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC222.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs similarity index 100% rename from crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index 2ae3723f49d74..c02f215200a10 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -7,3 +7,15 @@ mod blocking_http_call; mod blocking_os_call; mod open_sleep_or_subprocess_call; mod sleep_forever_call; + +pub(crate) use async_function_with_timeout::*; +pub(crate) use sync_call::*; +pub(crate) use timeout_without_await::*; +pub(crate) use unneeded_sleep::*; +pub(crate) use zero_sleep_call::*; + +mod async_function_with_timeout; +mod sync_call; +mod timeout_without_await; +mod unneeded_sleep; +mod zero_sleep_call; diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs similarity index 97% rename from crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs index 2f824ab41a26c..383658042b5dc 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs @@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::pad; -use crate::rules::flake8_trio::method_name::MethodName; +use crate::rules::flake8_async::helpers::MethodName; /// ## What it does /// Checks for calls to trio functions that are not immediately awaited. diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs b/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs similarity index 97% rename from crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs index d0707d32bc4a2..67e6d26ba3d5f 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs @@ -6,7 +6,7 @@ use ruff_python_ast::{StmtWith, WithItem}; use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; -use crate::rules::flake8_trio::method_name::MethodName; +use crate::rules::flake8_async::helpers::MethodName; /// ## What it does /// Checks for trio functions that should contain await but don't. diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs similarity index 100% rename from crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs similarity index 100% rename from crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap similarity index 91% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap index 39514476b5e09..fe22d6a3c34ab 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC100_ASYNC100.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- ASYNC100.py:5:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC105_ASYNC105.py.snap similarity index 99% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC105_ASYNC105.py.snap index 726bda2eca1eb..1595cdc008549 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC105_ASYNC105.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC105_ASYNC105.py.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- ASYNC105.py:30:5: ASYNC105 [*] Call to `trio.aclose_forcefully` is not immediately awaited | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC109_ASYNC109.py.snap similarity index 88% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC109_ASYNC109.py.snap index 0131a275f5ea4..c196c1af9d151 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC109_ASYNC109.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC109_ASYNC109.py.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- ASYNC109.py:8:16: ASYNC109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC110_ASYNC110.py.snap similarity index 89% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC110_ASYNC110.py.snap index 2604c633e7e3c..fe99c8f822450 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC110_ASYNC110.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC110_ASYNC110.py.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- ASYNC110.py:5:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop | diff --git a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap similarity index 98% rename from crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap rename to crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap index b020ebb031ec4..71d341d400af1 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__ASYNC115_ASYNC115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap @@ -1,5 +1,5 @@ --- -source: crates/ruff_linter/src/rules/flake8_trio/mod.rs +source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- ASYNC115.py:5:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` | diff --git a/crates/ruff_linter/src/rules/flake8_trio/mod.rs b/crates/ruff_linter/src/rules/flake8_trio/mod.rs deleted file mode 100644 index 995359660cf02..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_trio/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/). -pub(super) mod method_name; -pub(crate) mod rules; - -#[cfg(test)] -mod tests { - use std::path::Path; - - use anyhow::Result; - use test_case::test_case; - - use crate::assert_messages; - use crate::registry::Rule; - use crate::settings::LinterSettings; - use crate::test::test_path; - - #[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))] - #[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))] - #[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))] - #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] - #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] - fn rules(rule_code: Rule, path: &Path) -> Result<()> { - let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); - let diagnostics = test_path( - Path::new("flake8_async").join(path).as_path(), - &LinterSettings::for_rule(rule_code), - )?; - assert_messages!(snapshot, diagnostics); - Ok(()) - } -} diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs deleted file mode 100644 index 3126b10224b93..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub(crate) use async_function_with_timeout::*; -pub(crate) use sync_call::*; -pub(crate) use timeout_without_await::*; -pub(crate) use unneeded_sleep::*; -pub(crate) use zero_sleep_call::*; - -mod async_function_with_timeout; -mod sync_call; -mod timeout_without_await; -mod unneeded_sleep; -mod zero_sleep_call; diff --git a/crates/ruff_linter/src/rules/mod.rs b/crates/ruff_linter/src/rules/mod.rs index 64b0128cae164..6240d93d12719 100644 --- a/crates/ruff_linter/src/rules/mod.rs +++ b/crates/ruff_linter/src/rules/mod.rs @@ -37,7 +37,6 @@ pub mod flake8_simplify; pub mod flake8_slots; pub mod flake8_tidy_imports; pub mod flake8_todos; -pub mod flake8_trio; pub mod flake8_type_checking; pub mod flake8_unused_arguments; pub mod flake8_use_pathlib; From 7406194c2da7039623060ad7fa9c95763ee4298b Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 20:51:27 -0400 Subject: [PATCH 03/22] update comment --- .../src/rules/flake8_async/rules/blocking_http_call.rs | 2 +- .../src/rules/flake8_async/rules/blocking_os_call.rs | 2 +- .../rules/flake8_async/rules/open_sleep_or_subprocess_call.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index 1318d4cbd705b..c76958cf907f0 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -60,7 +60,7 @@ fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool { ) } -/// ASYNC100 +/// ASYNC210 pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) { if checker.semantic().in_async_context() { if checker diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs index 59848aeb7d040..43e792277cd67 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs @@ -41,7 +41,7 @@ impl Violation for BlockingOsCallInAsyncFunction { } } -/// ASYNC102 +/// ASYNC222 pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) { if checker.semantic().seen_module(Modules::OS) { if checker.semantic().in_async_context() { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs index 389a39d370a4a..d9bf07cfeba42 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs @@ -39,7 +39,7 @@ impl Violation for OpenSleepOrSubprocessInAsyncFunction { } } -/// ASYNC101 +/// ASYNC220 pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::ExprCall) { if !checker.semantic().in_async_context() { return; From a56569583130ab14b5b0e9f1f551a1dadd9217e9 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 20:54:50 -0400 Subject: [PATCH 04/22] format --- .../src/checkers/ast/analyze/expression.rs | 4 ++-- .../src/checkers/ast/analyze/statement.rs | 10 +++++----- .../ruff_linter/src/rules/flake8_async/rules/mod.rs | 12 +++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index f21fe84b653bd..266a1e7f63aed 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -15,8 +15,8 @@ use crate::rules::{ flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django, flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging, flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, - flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, - flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff, + flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, flynt, numpy, + pandas_vet, pep8_naming, pycodestyle, pyflakes, pylint, pyupgrade, refurb, ruff, }; use crate::settings::types::PythonVersion; diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index cb2035a03b921..030cfe2449041 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -8,11 +8,11 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::registry::Rule; use crate::rules::{ - airflow, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_debugger, - flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, flake8_pyi, - flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots, - flake8_tidy_imports, flake8_async, flake8_type_checking, mccabe, pandas_vet, pep8_naming, - perflint, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops, + airflow, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, + flake8_debugger, flake8_django, flake8_errmsg, flake8_import_conventions, flake8_pie, + flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return, flake8_simplify, flake8_slots, + flake8_tidy_imports, flake8_type_checking, mccabe, pandas_vet, pep8_naming, perflint, + pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, tryceratops, }; use crate::settings::types::PythonVersion; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index c02f215200a10..ce80ee2a3640d 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -1,20 +1,18 @@ +pub(crate) use async_function_with_timeout::*; pub(crate) use blocking_http_call::*; pub(crate) use blocking_os_call::*; pub(crate) use open_sleep_or_subprocess_call::*; pub(crate) use sleep_forever_call::*; - -mod blocking_http_call; -mod blocking_os_call; -mod open_sleep_or_subprocess_call; -mod sleep_forever_call; - -pub(crate) use async_function_with_timeout::*; pub(crate) use sync_call::*; pub(crate) use timeout_without_await::*; pub(crate) use unneeded_sleep::*; pub(crate) use zero_sleep_call::*; mod async_function_with_timeout; +mod blocking_http_call; +mod blocking_os_call; +mod open_sleep_or_subprocess_call; +mod sleep_forever_call; mod sync_call; mod timeout_without_await; mod unneeded_sleep; From 4e8d7870686e157007e6f92225c70330d3cfc1bc Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 21:16:12 -0400 Subject: [PATCH 05/22] update ASYNC210 to more closely match flake8-async and port more test cases --- .../test/fixtures/flake8_async/ASYNC210.py | 58 ++++- .../flake8_async/rules/blocking_http_call.rs | 3 +- ...e8_async__tests__ASYNC210_ASYNC210.py.snap | 222 ++++++++++++++++-- 3 files changed, 261 insertions(+), 22 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py index 532273a7b4676..4a006e2ca3844 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC210.py @@ -1,23 +1,69 @@ -import urllib.request +import urllib import requests import httpx +import urllib3 async def foo(): - urllib.request.urlopen("http://example.com/foo/bar").read() + urllib.request.urlopen("http://example.com/foo/bar").read() # ASYNC210 async def foo(): - requests.get() + requests.get() # ASYNC210 async def foo(): - httpx.get() + httpx.get() # ASYNC210 async def foo(): - requests.post() + requests.post() # ASYNC210 async def foo(): - httpx.post() + httpx.post() # ASYNC210 + + +async def foo(): + requests.get() # ASYNC210 + requests.get(...) # ASYNC210 + requests.get # Ok + print(requests.get()) # ASYNC210 + print(requests.get(requests.get())) # ASYNC210 + + requests.options() # ASYNC210 + requests.head() # ASYNC210 + requests.post() # ASYNC210 + requests.put() # ASYNC210 + requests.patch() # ASYNC210 + requests.delete() # ASYNC210 + requests.foo() + + httpx.options("") # ASYNC210 + httpx.head("") # ASYNC210 + httpx.post("") # ASYNC210 + httpx.put("") # ASYNC210 + httpx.patch("") # ASYNC210 + httpx.delete("") # ASYNC210 + httpx.foo() # Ok + + urllib3.request() # ASYNC210 + urllib3.request(...) # ASYNC210 + + urllib.request.urlopen("") # ASYNC210 + + r = {} + r.get("not a sync http client") # Ok + + +async def bar(): + + def request(): + pass + + request() # Ok + + def urlopen(): + pass + + urlopen() # Ok diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index c76958cf907f0..d8df2a7148146 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -44,7 +44,8 @@ impl Violation for BlockingHttpCallInAsyncFunction { fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool { matches!( qualified_name.segments(), - ["urllib", "request", "urlopen"] + ["urllib", "request", "urlopen"] | + ["urllib3", "request"] | [ "httpx" | "requests", "get" diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap index 6807e8db02745..8ca70a938797f 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC210_ASYNC210.py.snap @@ -1,37 +1,229 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC210.py:7:5: ASYNC210 Async functions should not call blocking HTTP methods +ASYNC210.py:8:5: ASYNC210 Async functions should not call blocking HTTP methods | -6 | async def foo(): -7 | urllib.request.urlopen("http://example.com/foo/bar").read() +7 | async def foo(): +8 | urllib.request.urlopen("http://example.com/foo/bar").read() # ASYNC210 | ^^^^^^^^^^^^^^^^^^^^^^ ASYNC210 | -ASYNC210.py:11:5: ASYNC210 Async functions should not call blocking HTTP methods +ASYNC210.py:12:5: ASYNC210 Async functions should not call blocking HTTP methods | -10 | async def foo(): -11 | requests.get() +11 | async def foo(): +12 | requests.get() # ASYNC210 | ^^^^^^^^^^^^ ASYNC210 | -ASYNC210.py:15:5: ASYNC210 Async functions should not call blocking HTTP methods +ASYNC210.py:16:5: ASYNC210 Async functions should not call blocking HTTP methods | -14 | async def foo(): -15 | httpx.get() +15 | async def foo(): +16 | httpx.get() # ASYNC210 | ^^^^^^^^^ ASYNC210 | -ASYNC210.py:19:5: ASYNC210 Async functions should not call blocking HTTP methods +ASYNC210.py:20:5: ASYNC210 Async functions should not call blocking HTTP methods | -18 | async def foo(): -19 | requests.post() +19 | async def foo(): +20 | requests.post() # ASYNC210 | ^^^^^^^^^^^^^ ASYNC210 | -ASYNC210.py:23:5: ASYNC210 Async functions should not call blocking HTTP methods +ASYNC210.py:24:5: ASYNC210 Async functions should not call blocking HTTP methods | -22 | async def foo(): -23 | httpx.post() +23 | async def foo(): +24 | httpx.post() # ASYNC210 | ^^^^^^^^^^ ASYNC210 | + +ASYNC210.py:28:5: ASYNC210 Async functions should not call blocking HTTP methods + | +27 | async def foo(): +28 | requests.get() # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +29 | requests.get(...) # ASYNC210 +30 | requests.get # Ok + | + +ASYNC210.py:29:5: ASYNC210 Async functions should not call blocking HTTP methods + | +27 | async def foo(): +28 | requests.get() # ASYNC210 +29 | requests.get(...) # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +30 | requests.get # Ok +31 | print(requests.get()) # ASYNC210 + | + +ASYNC210.py:31:11: ASYNC210 Async functions should not call blocking HTTP methods + | +29 | requests.get(...) # ASYNC210 +30 | requests.get # Ok +31 | print(requests.get()) # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +32 | print(requests.get(requests.get())) # ASYNC210 + | + +ASYNC210.py:32:11: ASYNC210 Async functions should not call blocking HTTP methods + | +30 | requests.get # Ok +31 | print(requests.get()) # ASYNC210 +32 | print(requests.get(requests.get())) # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +33 | +34 | requests.options() # ASYNC210 + | + +ASYNC210.py:32:24: ASYNC210 Async functions should not call blocking HTTP methods + | +30 | requests.get # Ok +31 | print(requests.get()) # ASYNC210 +32 | print(requests.get(requests.get())) # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +33 | +34 | requests.options() # ASYNC210 + | + +ASYNC210.py:34:5: ASYNC210 Async functions should not call blocking HTTP methods + | +32 | print(requests.get(requests.get())) # ASYNC210 +33 | +34 | requests.options() # ASYNC210 + | ^^^^^^^^^^^^^^^^ ASYNC210 +35 | requests.head() # ASYNC210 +36 | requests.post() # ASYNC210 + | + +ASYNC210.py:35:5: ASYNC210 Async functions should not call blocking HTTP methods + | +34 | requests.options() # ASYNC210 +35 | requests.head() # ASYNC210 + | ^^^^^^^^^^^^^ ASYNC210 +36 | requests.post() # ASYNC210 +37 | requests.put() # ASYNC210 + | + +ASYNC210.py:36:5: ASYNC210 Async functions should not call blocking HTTP methods + | +34 | requests.options() # ASYNC210 +35 | requests.head() # ASYNC210 +36 | requests.post() # ASYNC210 + | ^^^^^^^^^^^^^ ASYNC210 +37 | requests.put() # ASYNC210 +38 | requests.patch() # ASYNC210 + | + +ASYNC210.py:37:5: ASYNC210 Async functions should not call blocking HTTP methods + | +35 | requests.head() # ASYNC210 +36 | requests.post() # ASYNC210 +37 | requests.put() # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +38 | requests.patch() # ASYNC210 +39 | requests.delete() # ASYNC210 + | + +ASYNC210.py:38:5: ASYNC210 Async functions should not call blocking HTTP methods + | +36 | requests.post() # ASYNC210 +37 | requests.put() # ASYNC210 +38 | requests.patch() # ASYNC210 + | ^^^^^^^^^^^^^^ ASYNC210 +39 | requests.delete() # ASYNC210 +40 | requests.foo() + | + +ASYNC210.py:39:5: ASYNC210 Async functions should not call blocking HTTP methods + | +37 | requests.put() # ASYNC210 +38 | requests.patch() # ASYNC210 +39 | requests.delete() # ASYNC210 + | ^^^^^^^^^^^^^^^ ASYNC210 +40 | requests.foo() + | + +ASYNC210.py:42:5: ASYNC210 Async functions should not call blocking HTTP methods + | +40 | requests.foo() +41 | +42 | httpx.options("") # ASYNC210 + | ^^^^^^^^^^^^^ ASYNC210 +43 | httpx.head("") # ASYNC210 +44 | httpx.post("") # ASYNC210 + | + +ASYNC210.py:43:5: ASYNC210 Async functions should not call blocking HTTP methods + | +42 | httpx.options("") # ASYNC210 +43 | httpx.head("") # ASYNC210 + | ^^^^^^^^^^ ASYNC210 +44 | httpx.post("") # ASYNC210 +45 | httpx.put("") # ASYNC210 + | + +ASYNC210.py:44:5: ASYNC210 Async functions should not call blocking HTTP methods + | +42 | httpx.options("") # ASYNC210 +43 | httpx.head("") # ASYNC210 +44 | httpx.post("") # ASYNC210 + | ^^^^^^^^^^ ASYNC210 +45 | httpx.put("") # ASYNC210 +46 | httpx.patch("") # ASYNC210 + | + +ASYNC210.py:45:5: ASYNC210 Async functions should not call blocking HTTP methods + | +43 | httpx.head("") # ASYNC210 +44 | httpx.post("") # ASYNC210 +45 | httpx.put("") # ASYNC210 + | ^^^^^^^^^ ASYNC210 +46 | httpx.patch("") # ASYNC210 +47 | httpx.delete("") # ASYNC210 + | + +ASYNC210.py:46:5: ASYNC210 Async functions should not call blocking HTTP methods + | +44 | httpx.post("") # ASYNC210 +45 | httpx.put("") # ASYNC210 +46 | httpx.patch("") # ASYNC210 + | ^^^^^^^^^^^ ASYNC210 +47 | httpx.delete("") # ASYNC210 +48 | httpx.foo() # Ok + | + +ASYNC210.py:47:5: ASYNC210 Async functions should not call blocking HTTP methods + | +45 | httpx.put("") # ASYNC210 +46 | httpx.patch("") # ASYNC210 +47 | httpx.delete("") # ASYNC210 + | ^^^^^^^^^^^^ ASYNC210 +48 | httpx.foo() # Ok + | + +ASYNC210.py:50:5: ASYNC210 Async functions should not call blocking HTTP methods + | +48 | httpx.foo() # Ok +49 | +50 | urllib3.request() # ASYNC210 + | ^^^^^^^^^^^^^^^ ASYNC210 +51 | urllib3.request(...) # ASYNC210 + | + +ASYNC210.py:51:5: ASYNC210 Async functions should not call blocking HTTP methods + | +50 | urllib3.request() # ASYNC210 +51 | urllib3.request(...) # ASYNC210 + | ^^^^^^^^^^^^^^^ ASYNC210 +52 | +53 | urllib.request.urlopen("") # ASYNC210 + | + +ASYNC210.py:53:5: ASYNC210 Async functions should not call blocking HTTP methods + | +51 | urllib3.request(...) # ASYNC210 +52 | +53 | urllib.request.urlopen("") # ASYNC210 + | ^^^^^^^^^^^^^^^^^^^^^^ ASYNC210 +54 | +55 | r = {} + | From f53aa0fa584eb48c827c3736408a9285aeb9f708 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 21:31:27 -0400 Subject: [PATCH 06/22] format --- .../src/rules/flake8_async/rules/blocking_http_call.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index d8df2a7148146..dc80120217ac8 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -44,8 +44,8 @@ impl Violation for BlockingHttpCallInAsyncFunction { fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool { matches!( qualified_name.segments(), - ["urllib", "request", "urlopen"] | - ["urllib3", "request"] + ["urllib", "request", "urlopen"] + | ["urllib3", "request"] | [ "httpx" | "requests", "get" From c79c88ed360e557d29c1cc61adc8de51e4ede5f3 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Thu, 14 Mar 2024 23:25:17 -0400 Subject: [PATCH 07/22] move `open` checks to ASYNC230 --- .../test/fixtures/flake8_async/ASYNC220.py | 57 -------- .../test/fixtures/flake8_async/ASYNC230.py | 82 ++++++++++++ .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + .../ruff_linter/src/rules/flake8_async/mod.rs | 1 + .../flake8_async/rules/blocking_open_call.rs | 124 ++++++++++++++++++ .../src/rules/flake8_async/rules/mod.rs | 2 + .../rules/open_sleep_or_subprocess_call.rs | 65 +-------- .../flake8_async/rules/process_invocation.rs | 0 ...e8_async__tests__ASYNC220_ASYNC220.py.snap | 73 ++--------- ...e8_async__tests__ASYNC230_ASYNC230.py.snap | 101 ++++++++++++++ ruff.schema.json | 2 + 12 files changed, 336 insertions(+), 175 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC230.py create mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC230_ASYNC230.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py index be070ccb48d6f..6efced43b3221 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py @@ -6,10 +6,6 @@ # Violation cases: -async def func(): - open("foo") - - async def func(): time.sleep(1) @@ -32,56 +28,3 @@ async def func(): async def func(): os.wait(12) - - -# Violation cases for pathlib: - - -async def func(): - Path("foo").open() # ASYNC220 - - -async def func(): - p = Path("foo") - p.open() # ASYNC220 - - -async def func(): - with Path("foo").open() as f: # ASYNC220 - pass - - -async def func() -> None: - p = Path("foo") - - async def bar(): - p.open() # ASYNC220 - - -async def func() -> None: - (p1, p2) = (Path("foo"), Path("bar")) - - p1.open() # ASYNC220 - - -# Non-violation cases for pathlib: - - -class Foo: - def open(self): - pass - - -async def func(): - Foo().open() # OK - - -async def func(): - def open(): - pass - - open() # OK - - -def func(): - Path("foo").open() # OK diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC230.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC230.py new file mode 100644 index 0000000000000..e38dc8df43519 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC230.py @@ -0,0 +1,82 @@ +import io +from pathlib import Path + + +async def foo(): + open("") # ASYNC230 + io.open_code("") # ASYNC230 + + with open(""): # ASYNC230 + ... + + with open("") as f: # ASYNC230 + ... + + with foo(), open(""): # ASYNC230 + ... + + async with open(""): # ASYNC230 + ... + + +def foo_sync(): + open("") + +# Violation cases: + + +async def func(): + open("foo") # ASYNC230 + + +# Violation cases for pathlib: + + +async def func(): + Path("foo").open() # ASYNC230 + + +async def func(): + p = Path("foo") + p.open() # ASYNC230 + + +async def func(): + with Path("foo").open() as f: # ASYNC230 + pass + + +async def func() -> None: + p = Path("foo") + + async def bar(): + p.open() # ASYNC230 + + +async def func() -> None: + (p1, p2) = (Path("foo"), Path("bar")) + + p1.open() # ASYNC230 + + +# Non-violation cases for pathlib: + + +class Foo: + def open(self): + pass + + +async def func(): + Foo().open() # OK + + +async def func(): + def open(): + pass + + open() # OK + + +def func(): + Path("foo").open() # OK diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 266a1e7f63aed..1b2cf36ef87d4 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -511,6 +511,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::BlockingOsCallInAsyncFunction) { flake8_async::rules::blocking_os_call(checker, call); } + if checker.enabled(Rule::BlockinOpenCallInAsyncFunction) { + flake8_async::rules::blocking_open_call(checker, call); + } if checker.enabled(Rule::SleepForeverCall) { flake8_async::rules::sleep_forever_call(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 718f0aca8cd01..10993074b62df 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -301,6 +301,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction), (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction), + (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockinOpenCallInAsyncFunction), // flake8-builtins (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index 3664a7b31d813..061d6ad8e657b 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -22,6 +22,7 @@ mod tests { #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] #[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC220.py"))] #[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC222.py"))] + #[test_case(Rule::BlockinOpenCallInAsyncFunction, Path::new("ASYNC230.py"))] #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs new file mode 100644 index 0000000000000..d40be87996868 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -0,0 +1,124 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::{analyze, SemanticModel}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks that async functions do not open files with blocking methods like `open`. +/// +/// ## Why is this bad? +/// Blocking an async function via a blocking call will block the entire +/// event loop, preventing it from executing other tasks while waiting for the +/// call to complete, negating the benefits of asynchronous programming. +/// +/// Instead of making a blocking call, use an equivalent asynchronous library +/// or function. +/// +/// ## Example +/// ```python +/// async def foo(): +/// with open('bar.txt') as f: +/// contents = f.read() +/// ``` +/// +/// Use instead: +/// ```python +/// import anyio +/// +/// async def foo(): +/// async with await open_file('bar.txt') as f: +/// contents = await f.read() +/// ``` +#[violation] +pub struct BlockinOpenCallInAsyncFunction; + +impl Violation for BlockinOpenCallInAsyncFunction { + #[derive_message_formats] + fn message(&self) -> String { + format!("Async functions should not open files with blocking methods like `open`") + } +} + +/// ASYNC230 +pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().in_async_context() { + return; + } + + if is_open_call(&call.func, checker.semantic()) + || is_open_call_from_pathlib(call.func.as_ref(), checker.semantic()) + { + checker.diagnostics.push(Diagnostic::new( + BlockinOpenCallInAsyncFunction, + call.func.range(), + )); + } +} + + +/// Returns `true` if the expression resolves to a blocking open call, like `open` or `Path().open()`. +fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + ["", "open"] + | ["io", "open"] + | ["io", "open_code"] + ) + }) +} + + +/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`. +fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { + let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { + return false; + }; + + if attr.as_str() != "open" { + return false; + } + + // First: is this an inlined call to `pathlib.Path.open`? + // ```python + // from pathlib import Path + // Path("foo").open() + // ``` + if let Expr::Call(call) = value.as_ref() { + let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) else { + return false; + }; + if qualified_name.segments() == ["pathlib", "Path"] { + return true; + } + } + + // Second, is this a call to `pathlib.Path.open` via a variable? + // ```python + // from pathlib import Path + // path = Path("foo") + // path.open() + // ``` + let Expr::Name(name) = value.as_ref() else { + return false; + }; + + let Some(binding_id) = semantic.resolve_name(name) else { + return false; + }; + + let binding = semantic.binding(binding_id); + + let Some(Expr::Call(call)) = analyze::typing::find_binding_value(binding, semantic) else { + return false; + }; + + semantic + .resolve_qualified_name(call.func.as_ref()) + .is_some_and(|qualified_name| qualified_name.segments() == ["pathlib", "Path"]) +} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index ce80ee2a3640d..8c4d5aa1c93bc 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -1,5 +1,6 @@ pub(crate) use async_function_with_timeout::*; pub(crate) use blocking_http_call::*; +pub(crate) use blocking_open_call::*; pub(crate) use blocking_os_call::*; pub(crate) use open_sleep_or_subprocess_call::*; pub(crate) use sleep_forever_call::*; @@ -10,6 +11,7 @@ pub(crate) use zero_sleep_call::*; mod async_function_with_timeout; mod blocking_http_call; +mod blocking_open_call; mod blocking_os_call; mod open_sleep_or_subprocess_call; mod sleep_forever_call; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs index d9bf07cfeba42..1eae2dbd8d849 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs @@ -1,14 +1,13 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; -use ruff_python_semantic::{analyze, SemanticModel}; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks that async functions do not contain calls to `open`, `time.sleep`, -/// or `subprocess` methods. +/// Checks that async functions do not contain calls to `time.sleep`, or `subprocess` methods. /// /// ## Why is this bad? /// Blocking an async function via a blocking call will block the entire @@ -35,7 +34,7 @@ pub struct OpenSleepOrSubprocessInAsyncFunction; impl Violation for OpenSleepOrSubprocessInAsyncFunction { #[derive_message_formats] fn message(&self) -> String { - format!("Async functions should not call `open`, `time.sleep`, or `subprocess` methods") + format!("Async functions should not call `time.sleep`, or `subprocess` methods") } } @@ -45,9 +44,7 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E return; } - if is_open_sleep_or_subprocess_call(&call.func, checker.semantic()) - || is_open_call_from_pathlib(call.func.as_ref(), checker.semantic()) - { + if is_sleep_or_subprocess_call(&call.func, checker.semantic()) { checker.diagnostics.push(Diagnostic::new( OpenSleepOrSubprocessInAsyncFunction, call.func.range(), @@ -57,14 +54,13 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E /// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or /// `subprocess.run`. -fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { +fn is_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_qualified_name(func) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["" | "builtins", "open"] - | ["time", "sleep"] + ["time", "sleep"] | [ "subprocess", "run" @@ -79,52 +75,3 @@ fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bo ) }) } - -/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`. -fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { - let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { - return false; - }; - - if attr.as_str() != "open" { - return false; - } - - // First: is this an inlined call to `pathlib.Path.open`? - // ```python - // from pathlib import Path - // Path("foo").open() - // ``` - if let Expr::Call(call) = value.as_ref() { - let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) else { - return false; - }; - if qualified_name.segments() == ["pathlib", "Path"] { - return true; - } - } - - // Second, is this a call to `pathlib.Path.open` via a variable? - // ```python - // from pathlib import Path - // path = Path("foo") - // path.open() - // ``` - let Expr::Name(name) = value.as_ref() else { - return false; - }; - - let Some(binding_id) = semantic.resolve_name(name) else { - return false; - }; - - let binding = semantic.binding(binding_id); - - let Some(Expr::Call(call)) = analyze::typing::find_binding_value(binding, semantic) else { - return false; - }; - - semantic - .resolve_qualified_name(call.func.as_ref()) - .is_some_and(|qualified_name| qualified_name.segments() == ["pathlib", "Path"]) -} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap index 745f042c6984c..ec9fa09b515d0 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap @@ -1,82 +1,37 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC220.py:10:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods +ASYNC220.py:10:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods | 9 | async def func(): -10 | open("foo") - | ^^^^ ASYNC220 - | - -ASYNC220.py:14:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -13 | async def func(): -14 | time.sleep(1) +10 | time.sleep(1) | ^^^^^^^^^^ ASYNC220 | -ASYNC220.py:18:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods +ASYNC220.py:14:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods | -17 | async def func(): -18 | subprocess.run("foo") +13 | async def func(): +14 | subprocess.run("foo") | ^^^^^^^^^^^^^^ ASYNC220 | -ASYNC220.py:22:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods +ASYNC220.py:18:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods | -21 | async def func(): -22 | subprocess.call("foo") +17 | async def func(): +18 | subprocess.call("foo") | ^^^^^^^^^^^^^^^ ASYNC220 | -ASYNC220.py:30:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods +ASYNC220.py:26:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods | -29 | async def func(): -30 | os.wait4(10) +25 | async def func(): +26 | os.wait4(10) | ^^^^^^^^ ASYNC220 | -ASYNC220.py:34:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -33 | async def func(): -34 | os.wait(12) - | ^^^^^^^ ASYNC220 - | - -ASYNC220.py:41:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -40 | async def func(): -41 | Path("foo").open() # ASYNC220 - | ^^^^^^^^^^^^^^^^ ASYNC220 +ASYNC220.py:30:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods | - -ASYNC220.py:46:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -44 | async def func(): -45 | p = Path("foo") -46 | p.open() # ASYNC220 - | ^^^^^^ ASYNC220 - | - -ASYNC220.py:50:10: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -49 | async def func(): -50 | with Path("foo").open() as f: # ASYNC220 - | ^^^^^^^^^^^^^^^^ ASYNC220 -51 | pass - | - -ASYNC220.py:58:9: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -57 | async def bar(): -58 | p.open() # ASYNC220 - | ^^^^^^ ASYNC220 - | - -ASYNC220.py:64:5: ASYNC220 Async functions should not call `open`, `time.sleep`, or `subprocess` methods - | -62 | (p1, p2) = (Path("foo"), Path("bar")) -63 | -64 | p1.open() # ASYNC220 +29 | async def func(): +30 | os.wait(12) | ^^^^^^^ ASYNC220 | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC230_ASYNC230.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC230_ASYNC230.py.snap new file mode 100644 index 0000000000000..c0b5faf4c47f3 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC230_ASYNC230.py.snap @@ -0,0 +1,101 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC230.py:6:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +5 | async def foo(): +6 | open("") # ASYNC230 + | ^^^^ ASYNC230 +7 | io.open_code("") # ASYNC230 + | + +ASYNC230.py:7:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +5 | async def foo(): +6 | open("") # ASYNC230 +7 | io.open_code("") # ASYNC230 + | ^^^^^^^^^^^^ ASYNC230 +8 | +9 | with open(""): # ASYNC230 + | + +ASYNC230.py:9:10: ASYNC230 Async functions should not open files with blocking methods like `open` + | + 7 | io.open_code("") # ASYNC230 + 8 | + 9 | with open(""): # ASYNC230 + | ^^^^ ASYNC230 +10 | ... + | + +ASYNC230.py:12:10: ASYNC230 Async functions should not open files with blocking methods like `open` + | +10 | ... +11 | +12 | with open("") as f: # ASYNC230 + | ^^^^ ASYNC230 +13 | ... + | + +ASYNC230.py:15:17: ASYNC230 Async functions should not open files with blocking methods like `open` + | +13 | ... +14 | +15 | with foo(), open(""): # ASYNC230 + | ^^^^ ASYNC230 +16 | ... + | + +ASYNC230.py:18:16: ASYNC230 Async functions should not open files with blocking methods like `open` + | +16 | ... +17 | +18 | async with open(""): # ASYNC230 + | ^^^^ ASYNC230 +19 | ... + | + +ASYNC230.py:29:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +28 | async def func(): +29 | open("foo") # ASYNC230 + | ^^^^ ASYNC230 + | + +ASYNC230.py:36:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +35 | async def func(): +36 | Path("foo").open() # ASYNC230 + | ^^^^^^^^^^^^^^^^ ASYNC230 + | + +ASYNC230.py:41:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +39 | async def func(): +40 | p = Path("foo") +41 | p.open() # ASYNC230 + | ^^^^^^ ASYNC230 + | + +ASYNC230.py:45:10: ASYNC230 Async functions should not open files with blocking methods like `open` + | +44 | async def func(): +45 | with Path("foo").open() as f: # ASYNC230 + | ^^^^^^^^^^^^^^^^ ASYNC230 +46 | pass + | + +ASYNC230.py:53:9: ASYNC230 Async functions should not open files with blocking methods like `open` + | +52 | async def bar(): +53 | p.open() # ASYNC230 + | ^^^^^^ ASYNC230 + | + +ASYNC230.py:59:5: ASYNC230 Async functions should not open files with blocking methods like `open` + | +57 | (p1, p2) = (Path("foo"), Path("bar")) +58 | +59 | p1.open() # ASYNC230 + | ^^^^^^^ ASYNC230 + | diff --git a/ruff.schema.json b/ruff.schema.json index 4c34911384432..e4dc73fdb4302 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2705,6 +2705,8 @@ "ASYNC22", "ASYNC220", "ASYNC222", + "ASYNC23", + "ASYNC230", "B", "B0", "B00", From 64908d4a4668af80b74528f9f2f5710c8339e041 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:02:46 -0400 Subject: [PATCH 08/22] modify ASYNC220, ASYNC222 and add ASYNC221 to match behavior of flake8-async --- .../test/fixtures/flake8_async/ASYNC220.py | 30 --- .../test/fixtures/flake8_async/ASYNC222.py | 13 - .../test/fixtures/flake8_async/ASYNC22x.py | 103 ++++++++ .../src/checkers/ast/analyze/expression.rs | 13 +- crates/ruff_linter/src/codes.rs | 5 +- .../ruff_linter/src/rules/flake8_async/mod.rs | 5 +- .../flake8_async/rules/blocking_open_call.rs | 6 +- .../flake8_async/rules/blocking_os_call.rs | 82 ------- .../rules/blocking_process_invocation.rs | 229 ++++++++++++++++++ .../src/rules/flake8_async/rules/mod.rs | 6 +- .../rules/open_sleep_or_subprocess_call.rs | 77 ------ .../flake8_async/rules/process_invocation.rs | 0 ...e8_async__tests__ASYNC220_ASYNC22x.py.snap | 77 ++++++ ...e8_async__tests__ASYNC221_ASYNC22x.py.snap | 226 +++++++++++++++++ ...e8_async__tests__ASYNC222_ASYNC22x.py.snap | 71 ++++++ ruff.schema.json | 1 + 16 files changed, 723 insertions(+), 221 deletions(-) delete mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py delete mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py delete mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs delete mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs delete mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py deleted file mode 100644 index 6efced43b3221..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC220.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import subprocess -import time -from pathlib import Path - -# Violation cases: - - -async def func(): - time.sleep(1) - - -async def func(): - subprocess.run("foo") - - -async def func(): - subprocess.call("foo") - - -async def func(): - subprocess.foo(0) - - -async def func(): - os.wait4(10) - - -async def func(): - os.wait(12) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py deleted file mode 100644 index 7912bcc9decab..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC222.py +++ /dev/null @@ -1,13 +0,0 @@ -import os - - -async def foo(): - os.popen() - - -async def foo(): - os.spawnl() - - -async def foo(): - os.fspath("foo") diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py new file mode 100644 index 0000000000000..21ffacd86d7a7 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py @@ -0,0 +1,103 @@ +import os +import subprocess +import time + +# Violation cases: + + +async def func(): + time.sleep(1) # ASYNC222 + + +async def func(): + subprocess.run("foo") # ASYNC221 + + +async def func(): + subprocess.call("foo") # ASYNC221 + + +async def func(): + subprocess.foo(0) # OK + + +async def func(): + os.wait4(10) # ASYNC222 + + +async def func(): + os.wait(12) # ASYNC222 + + +async def foo(): + await async_fun( + subprocess.getoutput() # ASYNC221 + ) + subprocess.Popen() # ASYNC220 + os.system() # ASYNC221 + + system() + os.system.anything() + os.anything() + + subprocess.run() # ASYNC221 + subprocess.call() # ASYNC221 + subprocess.check_call() # ASYNC221 + subprocess.check_output() # ASYNC221 + subprocess.getoutput() # ASYNC221 + subprocess.getstatusoutput() # ASYNC221 + + await async_fun( + subprocess.getoutput() # ASYNC221 + ) + + subprocess.anything() + subprocess.foo() + subprocess.bar.foo() + subprocess() + + os.posix_spawn() # ASYNC221 + os.posix_spawnp() # ASYNC221 + + os.spawn() + os.spawn + os.spawnllll() + + os.spawnl() # ASYNC221 + os.spawnle() # ASYNC221 + os.spawnlp() # ASYNC221 + os.spawnlpe() # ASYNC221 + os.spawnv() # ASYNC221 + os.spawnve() # ASYNC221 + os.spawnvp() # ASYNC221 + os.spawnvpe() # ASYNC221 + + P_NOWAIT = os.P_NOWAIT + + # if mode is given, and is not os.P_WAIT: ASYNC220 + os.spawnl(os.P_NOWAIT) # ASYNC220 + os.spawnl(P_NOWAIT) # ASYNC220 + os.spawnl(mode=os.P_NOWAIT) # ASYNC220 + os.spawnl(mode=P_NOWAIT) # ASYNC220 + + P_WAIT = os.P_WAIT + + # if it is P_WAIT, ASYNC221 + os.spawnl(P_WAIT) # ASYNC221 + os.spawnl(mode=os.P_WAIT) # ASYNC221 + os.spawnl(mode=P_WAIT) # ASYNC221 + + # other weird cases: ASYNC220 + os.spawnl(0) # ASYNC220 + os.spawnl(1) # ASYNC220 + os.spawnl(foo()) # ASYNC220 + + # ASYNC222 + os.wait() # ASYNC222 + os.wait3() # ASYNC222 + os.wait4() # ASYNC222 + os.waitid() # ASYNC222 + os.waitpid() # ASYNC222 + + os.waitpi() + os.waiti() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 1b2cf36ef87d4..09d322b4ada73 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -505,15 +505,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::BlockingHttpCallInAsyncFunction) { flake8_async::rules::blocking_http_call(checker, call); } - if checker.enabled(Rule::OpenSleepOrSubprocessInAsyncFunction) { - flake8_async::rules::open_sleep_or_subprocess_call(checker, call); - } - if checker.enabled(Rule::BlockingOsCallInAsyncFunction) { - flake8_async::rules::blocking_os_call(checker, call); - } if checker.enabled(Rule::BlockinOpenCallInAsyncFunction) { flake8_async::rules::blocking_open_call(checker, call); } + if checker.any_enabled(&[ + Rule::CreateSubprocessInAsyncFunction, + Rule::RunProcessInAsyncFunction, + Rule::WaitForProcessInAsyncFunction, + ]) { + flake8_async::rules::blocking_process_invocation(checker, call); + } if checker.enabled(Rule::SleepForeverCall) { flake8_async::rules::sleep_forever_call(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 10993074b62df..a05163c725190 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -299,8 +299,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall), (Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall), (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), - (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction), - (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOsCallInAsyncFunction), + (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction), + (Flake8Async, "221") => (RuleGroup::Preview, rules::flake8_async::rules::RunProcessInAsyncFunction), + (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction), (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockinOpenCallInAsyncFunction), // flake8-builtins diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index 061d6ad8e657b..24679b9be6e79 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -20,8 +20,9 @@ mod tests { #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] - #[test_case(Rule::OpenSleepOrSubprocessInAsyncFunction, Path::new("ASYNC220.py"))] - #[test_case(Rule::BlockingOsCallInAsyncFunction, Path::new("ASYNC222.py"))] + #[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))] + #[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))] + #[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::BlockinOpenCallInAsyncFunction, Path::new("ASYNC230.py"))] #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index d40be87996868..e579275b05da4 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -58,7 +58,6 @@ pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) { } } - /// Returns `true` if the expression resolves to a blocking open call, like `open` or `Path().open()`. fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool { semantic @@ -66,14 +65,11 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool { .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["", "open"] - | ["io", "open"] - | ["io", "open_code"] + ["", "open"] | ["io", "open"] | ["io", "open_code"] ) }) } - /// Returns `true` if an expression resolves to a call to `pathlib.Path.open`. fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs deleted file mode 100644 index 43e792277cd67..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs +++ /dev/null @@ -1,82 +0,0 @@ -use ruff_python_ast::ExprCall; - -use ruff_diagnostics::{Diagnostic, Violation}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::name::QualifiedName; -use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; - -use crate::checkers::ast::Checker; - -/// ## What it does -/// Checks that async functions do not contain calls to blocking synchronous -/// process calls via the `os` module. -/// -/// ## Why is this bad? -/// Blocking an async function via a blocking call will block the entire -/// event loop, preventing it from executing other tasks while waiting for the -/// call to complete, negating the benefits of asynchronous programming. -/// -/// Instead of making a blocking call, use an equivalent asynchronous library -/// or function. -/// -/// ## Example -/// ```python -/// async def foo(): -/// os.popen() -/// ``` -/// -/// Use instead: -/// ```python -/// def foo(): -/// os.popen() -/// ``` -#[violation] -pub struct BlockingOsCallInAsyncFunction; - -impl Violation for BlockingOsCallInAsyncFunction { - #[derive_message_formats] - fn message(&self) -> String { - format!("Async functions should not call synchronous `os` methods") - } -} - -/// ASYNC222 -pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) { - if checker.semantic().seen_module(Modules::OS) { - if checker.semantic().in_async_context() { - if checker - .semantic() - .resolve_qualified_name(call.func.as_ref()) - .as_ref() - .is_some_and(is_unsafe_os_method) - { - checker.diagnostics.push(Diagnostic::new( - BlockingOsCallInAsyncFunction, - call.func.range(), - )); - } - } - } -} - -fn is_unsafe_os_method(qualified_name: &QualifiedName) -> bool { - matches!( - qualified_name.segments(), - [ - "os", - "popen" - | "posix_spawn" - | "posix_spawnp" - | "spawnl" - | "spawnle" - | "spawnlp" - | "spawnlpe" - | "spawnv" - | "spawnve" - | "spawnvp" - | "spawnvpe" - | "system" - ] - ) -} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs new file mode 100644 index 0000000000000..0ff8029b7a00e --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -0,0 +1,229 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::analyze::typing::find_assigned_value; +use ruff_python_semantic::SemanticModel; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::registry::Rule; + +/// ## What it does +/// Checks that async functions do not create subprocesses with blocking methods. +/// +/// ## Why is this bad? +/// Blocking an async function via a blocking call will block the entire +/// event loop, preventing it from executing other tasks while waiting for the +/// call to complete, negating the benefits of asynchronous programming. +/// +/// Instead of making a blocking call, use an equivalent asynchronous library +/// or function. +/// +/// ## Example +/// ```python +/// async def foo(): +/// os.popen(cmd) +/// ``` +/// +/// Use instead: +/// ```python +/// async def foo(): +/// asyncio.create_subprocess_shell(cmd) +/// ``` +#[violation] +pub struct CreateSubprocessInAsyncFunction; + +impl Violation for CreateSubprocessInAsyncFunction { + #[derive_message_formats] + fn message(&self) -> String { + format!("Async functions should not create subprocesses with blocking methods") + } +} + +/// ## What it does +/// Checks that async functions do not run processes with blocking methods. +/// +/// ## Why is this bad? +/// Blocking an async function via a blocking call will block the entire +/// event loop, preventing it from executing other tasks while waiting for the +/// call to complete, negating the benefits of asynchronous programming. +/// +/// Instead of making a blocking call, use an equivalent asynchronous library +/// or function. +/// +/// ## Example +/// ```python +/// async def foo(): +/// subprocess.run(cmd) +/// ``` +/// +/// Use instead: +/// ```python +/// async def foo(): +/// asyncio.create_subprocess_shell(cmd) +/// ``` +#[violation] +pub struct RunProcessInAsyncFunction; + +impl Violation for RunProcessInAsyncFunction { + #[derive_message_formats] + fn message(&self) -> String { + format!("Async functions should not run processes with blocking methods") + } +} + +/// ## What it does +/// Checks that async functions do not wait on processes with blocking methods. +/// +/// ## Why is this bad? +/// Blocking an async function via a blocking call will block the entire +/// event loop, preventing it from executing other tasks while waiting for the +/// call to complete, negating the benefits of asynchronous programming. +/// +/// Instead of making a blocking call, use an equivalent asynchronous library +/// or function. +/// +/// ## Example +/// ```python +/// async def foo(): +/// os.waitpid(0) +/// ``` +/// +/// Use instead: +/// ```python +/// def wait_for_process(): +/// os.waitpid(0) +/// +/// async def foo(): +/// await asyncio.loop.run_in_executor(None, wait_for_process) +/// ``` +#[violation] +pub struct WaitForProcessInAsyncFunction; + +impl Violation for WaitForProcessInAsyncFunction { + #[derive_message_formats] + fn message(&self) -> String { + format!("Async functions should not wait on processes with blocking methods") + } +} + +/// ASYNC220, ASYNC221, ASYNC222 +pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().in_async_context() { + return; + } + + if checker.enabled(Rule::CreateSubprocessInAsyncFunction) { + if is_subprocess_call(&call.func, checker.semantic()) + || (is_os_spawn(&call.func, checker.semantic()) && !is_p_wait(&call, checker.semantic())) + { + checker.diagnostics.push(Diagnostic::new( + CreateSubprocessInAsyncFunction, + call.func.range(), + )); + } + } + + if checker.enabled(Rule::RunProcessInAsyncFunction) { + if is_run_process_call(&call.func, checker.semantic()) + || (is_os_spawn(&call.func, checker.semantic()) && is_p_wait(&call, checker.semantic())) + { + checker.diagnostics.push(Diagnostic::new( + RunProcessInAsyncFunction, + call.func.range(), + )); + } + } + + if checker.enabled(Rule::WaitForProcessInAsyncFunction) { + if is_wait_for_process_call(&call.func, checker.semantic()) { + checker.diagnostics.push(Diagnostic::new( + WaitForProcessInAsyncFunction, + call.func.range(), + )); + } + } +} + +fn is_os_spawn(func: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + [ + "os", + "spawnl" + | "spawnle" + | "spawnlp" + | "spawnlpe" + | "spawnv" + | "spawnve" + | "spawnvp" + | "spawnvpe" + ] + ) + }) +} + +fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool { + let Some(arg) = call.arguments.find_argument("mode", 0) else { + return true; + }; + + if let Some(qualified_name) = semantic.resolve_qualified_name(arg) { + return matches!(qualified_name.segments(), ["os", "P_WAIT"]); + } else if let Expr::Name(ast::ExprName { id, .. }) = arg { + let Some(value) = find_assigned_value(id, semantic) else { + return false; + }; + if let Some(qualified_name) = semantic.resolve_qualified_name(value) { + return matches!(qualified_name.segments(), ["os", "P_WAIT"]); + } + } + false +} + +fn is_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + ["subprocess", "Popen"] | ["os", "popen"] + ) + }) +} + +fn is_run_process_call(func: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + ["os", "system"] + | ["os", "posix_spawn"] + | ["os", "posix_spawnp"] + | [ + "subprocess", + "run" + | "call" + | "check_call" + | "check_output" + | "getoutput" + | "getstatusoutput" + ] + ) + }) +} + +fn is_wait_for_process_call(func: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] | ["time", "sleep"] + ) + }) +} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index 8c4d5aa1c93bc..4686a3008ff90 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -1,8 +1,7 @@ pub(crate) use async_function_with_timeout::*; pub(crate) use blocking_http_call::*; pub(crate) use blocking_open_call::*; -pub(crate) use blocking_os_call::*; -pub(crate) use open_sleep_or_subprocess_call::*; +pub(crate) use blocking_process_invocation::*; pub(crate) use sleep_forever_call::*; pub(crate) use sync_call::*; pub(crate) use timeout_without_await::*; @@ -12,8 +11,7 @@ pub(crate) use zero_sleep_call::*; mod async_function_with_timeout; mod blocking_http_call; mod blocking_open_call; -mod blocking_os_call; -mod open_sleep_or_subprocess_call; +mod blocking_process_invocation; mod sleep_forever_call; mod sync_call; mod timeout_without_await; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs deleted file mode 100644 index 1eae2dbd8d849..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs +++ /dev/null @@ -1,77 +0,0 @@ -use ruff_diagnostics::{Diagnostic, Violation}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr}; -use ruff_python_semantic::SemanticModel; -use ruff_text_size::Ranged; - -use crate::checkers::ast::Checker; - -/// ## What it does -/// Checks that async functions do not contain calls to `time.sleep`, or `subprocess` methods. -/// -/// ## Why is this bad? -/// Blocking an async function via a blocking call will block the entire -/// event loop, preventing it from executing other tasks while waiting for the -/// call to complete, negating the benefits of asynchronous programming. -/// -/// Instead of making a blocking call, use an equivalent asynchronous library -/// or function. -/// -/// ## Example -/// ```python -/// async def foo(): -/// time.sleep(1000) -/// ``` -/// -/// Use instead: -/// ```python -/// async def foo(): -/// await asyncio.sleep(1000) -/// ``` -#[violation] -pub struct OpenSleepOrSubprocessInAsyncFunction; - -impl Violation for OpenSleepOrSubprocessInAsyncFunction { - #[derive_message_formats] - fn message(&self) -> String { - format!("Async functions should not call `time.sleep`, or `subprocess` methods") - } -} - -/// ASYNC220 -pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::ExprCall) { - if !checker.semantic().in_async_context() { - return; - } - - if is_sleep_or_subprocess_call(&call.func, checker.semantic()) { - checker.diagnostics.push(Diagnostic::new( - OpenSleepOrSubprocessInAsyncFunction, - call.func.range(), - )); - } -} - -/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or -/// `subprocess.run`. -fn is_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["time", "sleep"] - | [ - "subprocess", - "run" - | "Popen" - | "call" - | "check_call" - | "check_output" - | "getoutput" - | "getstatusoutput" - ] - | ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] - ) - }) -} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/process_invocation.rs deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap new file mode 100644 index 0000000000000..ee54a873feeb4 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap @@ -0,0 +1,77 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC22x.py:36:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +34 | subprocess.getoutput() # ASYNC221 +35 | ) +36 | subprocess.Popen() # ASYNC220 + | ^^^^^^^^^^^^^^^^ ASYNC220 +37 | os.system() # ASYNC221 + | + +ASYNC22x.py:78:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +77 | # if mode is given, and is not os.P_WAIT: ASYNC220 +78 | os.spawnl(os.P_NOWAIT) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +79 | os.spawnl(P_NOWAIT) # ASYNC220 +80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 + | + +ASYNC22x.py:79:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +77 | # if mode is given, and is not os.P_WAIT: ASYNC220 +78 | os.spawnl(os.P_NOWAIT) # ASYNC220 +79 | os.spawnl(P_NOWAIT) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 + | + +ASYNC22x.py:80:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +78 | os.spawnl(os.P_NOWAIT) # ASYNC220 +79 | os.spawnl(P_NOWAIT) # ASYNC220 +80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 + | + +ASYNC22x.py:81:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +79 | os.spawnl(P_NOWAIT) # ASYNC220 +80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +82 | +83 | P_WAIT = os.P_WAIT + | + +ASYNC22x.py:91:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +90 | # other weird cases: ASYNC220 +91 | os.spawnl(0) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +92 | os.spawnl(1) # ASYNC220 +93 | os.spawnl(foo()) # ASYNC220 + | + +ASYNC22x.py:92:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +90 | # other weird cases: ASYNC220 +91 | os.spawnl(0) # ASYNC220 +92 | os.spawnl(1) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +93 | os.spawnl(foo()) # ASYNC220 + | + +ASYNC22x.py:93:5: ASYNC220 Async functions should not create subprocesses with blocking methods + | +91 | os.spawnl(0) # ASYNC220 +92 | os.spawnl(1) # ASYNC220 +93 | os.spawnl(foo()) # ASYNC220 + | ^^^^^^^^^ ASYNC220 +94 | +95 | # ASYNC222 + | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap new file mode 100644 index 0000000000000..d5fa49a3a0f11 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap @@ -0,0 +1,226 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC22x.py:13:5: ASYNC221 Async functions should not run processes with blocking methods + | +12 | async def func(): +13 | subprocess.run("foo") # ASYNC221 + | ^^^^^^^^^^^^^^ ASYNC221 + | + +ASYNC22x.py:17:5: ASYNC221 Async functions should not run processes with blocking methods + | +16 | async def func(): +17 | subprocess.call("foo") # ASYNC221 + | ^^^^^^^^^^^^^^^ ASYNC221 + | + +ASYNC22x.py:34:9: ASYNC221 Async functions should not run processes with blocking methods + | +32 | async def foo(): +33 | await async_fun( +34 | subprocess.getoutput() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 +35 | ) +36 | subprocess.Popen() # ASYNC220 + | + +ASYNC22x.py:37:5: ASYNC221 Async functions should not run processes with blocking methods + | +35 | ) +36 | subprocess.Popen() # ASYNC220 +37 | os.system() # ASYNC221 + | ^^^^^^^^^ ASYNC221 +38 | +39 | system() + | + +ASYNC22x.py:43:5: ASYNC221 Async functions should not run processes with blocking methods + | +41 | os.anything() +42 | +43 | subprocess.run() # ASYNC221 + | ^^^^^^^^^^^^^^ ASYNC221 +44 | subprocess.call() # ASYNC221 +45 | subprocess.check_call() # ASYNC221 + | + +ASYNC22x.py:44:5: ASYNC221 Async functions should not run processes with blocking methods + | +43 | subprocess.run() # ASYNC221 +44 | subprocess.call() # ASYNC221 + | ^^^^^^^^^^^^^^^ ASYNC221 +45 | subprocess.check_call() # ASYNC221 +46 | subprocess.check_output() # ASYNC221 + | + +ASYNC22x.py:45:5: ASYNC221 Async functions should not run processes with blocking methods + | +43 | subprocess.run() # ASYNC221 +44 | subprocess.call() # ASYNC221 +45 | subprocess.check_call() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^^ ASYNC221 +46 | subprocess.check_output() # ASYNC221 +47 | subprocess.getoutput() # ASYNC221 + | + +ASYNC22x.py:46:5: ASYNC221 Async functions should not run processes with blocking methods + | +44 | subprocess.call() # ASYNC221 +45 | subprocess.check_call() # ASYNC221 +46 | subprocess.check_output() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^^^^ ASYNC221 +47 | subprocess.getoutput() # ASYNC221 +48 | subprocess.getstatusoutput() # ASYNC221 + | + +ASYNC22x.py:47:5: ASYNC221 Async functions should not run processes with blocking methods + | +45 | subprocess.check_call() # ASYNC221 +46 | subprocess.check_output() # ASYNC221 +47 | subprocess.getoutput() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 +48 | subprocess.getstatusoutput() # ASYNC221 + | + +ASYNC22x.py:48:5: ASYNC221 Async functions should not run processes with blocking methods + | +46 | subprocess.check_output() # ASYNC221 +47 | subprocess.getoutput() # ASYNC221 +48 | subprocess.getstatusoutput() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC221 +49 | +50 | await async_fun( + | + +ASYNC22x.py:51:9: ASYNC221 Async functions should not run processes with blocking methods + | +50 | await async_fun( +51 | subprocess.getoutput() # ASYNC221 + | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 +52 | ) + | + +ASYNC22x.py:59:5: ASYNC221 Async functions should not run processes with blocking methods + | +57 | subprocess() +58 | +59 | os.posix_spawn() # ASYNC221 + | ^^^^^^^^^^^^^^ ASYNC221 +60 | os.posix_spawnp() # ASYNC221 + | + +ASYNC22x.py:60:5: ASYNC221 Async functions should not run processes with blocking methods + | +59 | os.posix_spawn() # ASYNC221 +60 | os.posix_spawnp() # ASYNC221 + | ^^^^^^^^^^^^^^^ ASYNC221 +61 | +62 | os.spawn() + | + +ASYNC22x.py:66:5: ASYNC221 Async functions should not run processes with blocking methods + | +64 | os.spawnllll() +65 | +66 | os.spawnl() # ASYNC221 + | ^^^^^^^^^ ASYNC221 +67 | os.spawnle() # ASYNC221 +68 | os.spawnlp() # ASYNC221 + | + +ASYNC22x.py:67:5: ASYNC221 Async functions should not run processes with blocking methods + | +66 | os.spawnl() # ASYNC221 +67 | os.spawnle() # ASYNC221 + | ^^^^^^^^^^ ASYNC221 +68 | os.spawnlp() # ASYNC221 +69 | os.spawnlpe() # ASYNC221 + | + +ASYNC22x.py:68:5: ASYNC221 Async functions should not run processes with blocking methods + | +66 | os.spawnl() # ASYNC221 +67 | os.spawnle() # ASYNC221 +68 | os.spawnlp() # ASYNC221 + | ^^^^^^^^^^ ASYNC221 +69 | os.spawnlpe() # ASYNC221 +70 | os.spawnv() # ASYNC221 + | + +ASYNC22x.py:69:5: ASYNC221 Async functions should not run processes with blocking methods + | +67 | os.spawnle() # ASYNC221 +68 | os.spawnlp() # ASYNC221 +69 | os.spawnlpe() # ASYNC221 + | ^^^^^^^^^^^ ASYNC221 +70 | os.spawnv() # ASYNC221 +71 | os.spawnve() # ASYNC221 + | + +ASYNC22x.py:70:5: ASYNC221 Async functions should not run processes with blocking methods + | +68 | os.spawnlp() # ASYNC221 +69 | os.spawnlpe() # ASYNC221 +70 | os.spawnv() # ASYNC221 + | ^^^^^^^^^ ASYNC221 +71 | os.spawnve() # ASYNC221 +72 | os.spawnvp() # ASYNC221 + | + +ASYNC22x.py:71:5: ASYNC221 Async functions should not run processes with blocking methods + | +69 | os.spawnlpe() # ASYNC221 +70 | os.spawnv() # ASYNC221 +71 | os.spawnve() # ASYNC221 + | ^^^^^^^^^^ ASYNC221 +72 | os.spawnvp() # ASYNC221 +73 | os.spawnvpe() # ASYNC221 + | + +ASYNC22x.py:72:5: ASYNC221 Async functions should not run processes with blocking methods + | +70 | os.spawnv() # ASYNC221 +71 | os.spawnve() # ASYNC221 +72 | os.spawnvp() # ASYNC221 + | ^^^^^^^^^^ ASYNC221 +73 | os.spawnvpe() # ASYNC221 + | + +ASYNC22x.py:73:5: ASYNC221 Async functions should not run processes with blocking methods + | +71 | os.spawnve() # ASYNC221 +72 | os.spawnvp() # ASYNC221 +73 | os.spawnvpe() # ASYNC221 + | ^^^^^^^^^^^ ASYNC221 +74 | +75 | P_NOWAIT = os.P_NOWAIT + | + +ASYNC22x.py:86:5: ASYNC221 Async functions should not run processes with blocking methods + | +85 | # if it is P_WAIT, ASYNC221 +86 | os.spawnl(P_WAIT) # ASYNC221 + | ^^^^^^^^^ ASYNC221 +87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 +88 | os.spawnl(mode=P_WAIT) # ASYNC221 + | + +ASYNC22x.py:87:5: ASYNC221 Async functions should not run processes with blocking methods + | +85 | # if it is P_WAIT, ASYNC221 +86 | os.spawnl(P_WAIT) # ASYNC221 +87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 + | ^^^^^^^^^ ASYNC221 +88 | os.spawnl(mode=P_WAIT) # ASYNC221 + | + +ASYNC22x.py:88:5: ASYNC221 Async functions should not run processes with blocking methods + | +86 | os.spawnl(P_WAIT) # ASYNC221 +87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 +88 | os.spawnl(mode=P_WAIT) # ASYNC221 + | ^^^^^^^^^ ASYNC221 +89 | +90 | # other weird cases: ASYNC220 + | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap new file mode 100644 index 0000000000000..3bdab672c7588 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap @@ -0,0 +1,71 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC22x.py:9:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +8 | async def func(): +9 | time.sleep(1) # ASYNC222 + | ^^^^^^^^^^ ASYNC222 + | + +ASYNC22x.py:25:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +24 | async def func(): +25 | os.wait4(10) # ASYNC222 + | ^^^^^^^^ ASYNC222 + | + +ASYNC22x.py:29:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +28 | async def func(): +29 | os.wait(12) # ASYNC222 + | ^^^^^^^ ASYNC222 + | + +ASYNC22x.py:96:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +95 | # ASYNC222 +96 | os.wait() # ASYNC222 + | ^^^^^^^ ASYNC222 +97 | os.wait3() # ASYNC222 +98 | os.wait4() # ASYNC222 + | + +ASYNC22x.py:97:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +95 | # ASYNC222 +96 | os.wait() # ASYNC222 +97 | os.wait3() # ASYNC222 + | ^^^^^^^^ ASYNC222 +98 | os.wait4() # ASYNC222 +99 | os.waitid() # ASYNC222 + | + +ASYNC22x.py:98:5: ASYNC222 Async functions should not wait on processes with blocking methods + | + 96 | os.wait() # ASYNC222 + 97 | os.wait3() # ASYNC222 + 98 | os.wait4() # ASYNC222 + | ^^^^^^^^ ASYNC222 + 99 | os.waitid() # ASYNC222 +100 | os.waitpid() # ASYNC222 + | + +ASYNC22x.py:99:5: ASYNC222 Async functions should not wait on processes with blocking methods + | + 97 | os.wait3() # ASYNC222 + 98 | os.wait4() # ASYNC222 + 99 | os.waitid() # ASYNC222 + | ^^^^^^^^^ ASYNC222 +100 | os.waitpid() # ASYNC222 + | + +ASYNC22x.py:100:5: ASYNC222 Async functions should not wait on processes with blocking methods + | + 98 | os.wait4() # ASYNC222 + 99 | os.waitid() # ASYNC222 +100 | os.waitpid() # ASYNC222 + | ^^^^^^^^^^ ASYNC222 +101 | +102 | os.waitpi() + | diff --git a/ruff.schema.json b/ruff.schema.json index e4dc73fdb4302..eadcb725445c8 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2704,6 +2704,7 @@ "ASYNC210", "ASYNC22", "ASYNC220", + "ASYNC221", "ASYNC222", "ASYNC23", "ASYNC230", From e97c4e9fbe8222e1eac1ca58dca86766434f6f29 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:03:13 -0400 Subject: [PATCH 09/22] format --- .../rules/flake8_async/rules/blocking_process_invocation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index 0ff8029b7a00e..8577164d1221c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -115,7 +115,8 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp if checker.enabled(Rule::CreateSubprocessInAsyncFunction) { if is_subprocess_call(&call.func, checker.semantic()) - || (is_os_spawn(&call.func, checker.semantic()) && !is_p_wait(&call, checker.semantic())) + || (is_os_spawn(&call.func, checker.semantic()) + && !is_p_wait(&call, checker.semantic())) { checker.diagnostics.push(Diagnostic::new( CreateSubprocessInAsyncFunction, From 27e2d23ed300669afff5cea3eb5774a9a933b741 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:06:18 -0400 Subject: [PATCH 10/22] typo --- crates/ruff_linter/src/checkers/ast/analyze/expression.rs | 2 +- crates/ruff_linter/src/codes.rs | 2 +- crates/ruff_linter/src/rules/flake8_async/mod.rs | 2 +- .../src/rules/flake8_async/rules/blocking_open_call.rs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 09d322b4ada73..c6f9c6fe7e427 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -505,7 +505,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::BlockingHttpCallInAsyncFunction) { flake8_async::rules::blocking_http_call(checker, call); } - if checker.enabled(Rule::BlockinOpenCallInAsyncFunction) { + if checker.enabled(Rule::BlockingOpenCallInAsyncFunction) { flake8_async::rules::blocking_open_call(checker, call); } if checker.any_enabled(&[ diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index a05163c725190..d73e553e3e50c 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -302,7 +302,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction), (Flake8Async, "221") => (RuleGroup::Preview, rules::flake8_async::rules::RunProcessInAsyncFunction), (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction), - (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockinOpenCallInAsyncFunction), + (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction), // flake8-builtins (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index 24679b9be6e79..1faf94ea5aee9 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -23,7 +23,7 @@ mod tests { #[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))] - #[test_case(Rule::BlockinOpenCallInAsyncFunction, Path::new("ASYNC230.py"))] + #[test_case(Rule::BlockingOpenCallInAsyncFunction, Path::new("ASYNC230.py"))] #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index e579275b05da4..b9e2b243eee63 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -33,9 +33,9 @@ use crate::checkers::ast::Checker; /// contents = await f.read() /// ``` #[violation] -pub struct BlockinOpenCallInAsyncFunction; +pub struct BlockingOpenCallInAsyncFunction; -impl Violation for BlockinOpenCallInAsyncFunction { +impl Violation for BlockingOpenCallInAsyncFunction { #[derive_message_formats] fn message(&self) -> String { format!("Async functions should not open files with blocking methods like `open`") @@ -52,7 +52,7 @@ pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) { || is_open_call_from_pathlib(call.func.as_ref(), checker.semantic()) { checker.diagnostics.push(Diagnostic::new( - BlockinOpenCallInAsyncFunction, + BlockingOpenCallInAsyncFunction, call.func.range(), )); } From a9446a109616d3f533a8ce58639a2ca08a74da86 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:14:05 -0400 Subject: [PATCH 11/22] clippy --- .../src/rules/flake8_async/rules/blocking_open_call.rs | 7 ++++--- .../flake8_async/rules/blocking_process_invocation.rs | 10 ++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index b9e2b243eee63..5d906bc07dfa4 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -20,7 +20,7 @@ use crate::checkers::ast::Checker; /// ## Example /// ```python /// async def foo(): -/// with open('bar.txt') as f: +/// with open("bar.txt") as f: /// contents = f.read() /// ``` /// @@ -28,8 +28,9 @@ use crate::checkers::ast::Checker; /// ```python /// import anyio /// +/// /// async def foo(): -/// async with await open_file('bar.txt') as f: +/// async with await anyio.open_file("bar.txt") as f: /// contents = await f.read() /// ``` #[violation] @@ -65,7 +66,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool { .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["", "open"] | ["io", "open"] | ["io", "open_code"] + ["" | "io", "open"] | ["io", "open_code"] ) }) } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index 8577164d1221c..934e558266161 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -94,6 +94,7 @@ impl Violation for RunProcessInAsyncFunction { /// def wait_for_process(): /// os.waitpid(0) /// +/// /// async def foo(): /// await asyncio.loop.run_in_executor(None, wait_for_process) /// ``` @@ -115,8 +116,7 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp if checker.enabled(Rule::CreateSubprocessInAsyncFunction) { if is_subprocess_call(&call.func, checker.semantic()) - || (is_os_spawn(&call.func, checker.semantic()) - && !is_p_wait(&call, checker.semantic())) + || (is_os_spawn(&call.func, checker.semantic()) && !is_p_wait(call, checker.semantic())) { checker.diagnostics.push(Diagnostic::new( CreateSubprocessInAsyncFunction, @@ -127,7 +127,7 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp if checker.enabled(Rule::RunProcessInAsyncFunction) { if is_run_process_call(&call.func, checker.semantic()) - || (is_os_spawn(&call.func, checker.semantic()) && is_p_wait(&call, checker.semantic())) + || (is_os_spawn(&call.func, checker.semantic()) && is_p_wait(call, checker.semantic())) { checker.diagnostics.push(Diagnostic::new( RunProcessInAsyncFunction, @@ -202,9 +202,7 @@ fn is_run_process_call(func: &Expr, semantic: &SemanticModel) -> bool { .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["os", "system"] - | ["os", "posix_spawn"] - | ["os", "posix_spawnp"] + ["os", "system" | "posix_spawn" | "posix_spawnp"] | [ "subprocess", "run" From 9bf827fff1bde50ce500e35cb06612507aef7a77 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:17:40 -0400 Subject: [PATCH 12/22] format docs --- .../src/rules/flake8_async/rules/blocking_open_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs index 5d906bc07dfa4..bfdbbae47320e 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_open_call.rs @@ -31,7 +31,7 @@ use crate::checkers::ast::Checker; /// /// async def foo(): /// async with await anyio.open_file("bar.txt") as f: -/// contents = await f.read() +/// contents = await f.read() /// ``` #[violation] pub struct BlockingOpenCallInAsyncFunction; From 3394e66396224b7a056f6102773ea2c9d39459bb Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:29:15 -0400 Subject: [PATCH 13/22] remove unreferenced snapshots --- ...e8_async__tests__ASYNC220_ASYNC220.py.snap | 37 ------------------- ...e8_async__tests__ASYNC222_ASYNC222.py.snap | 16 -------- 2 files changed, 53 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap delete mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap deleted file mode 100644 index ec9fa09b515d0..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC220.py.snap +++ /dev/null @@ -1,37 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_async/mod.rs ---- -ASYNC220.py:10:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods - | - 9 | async def func(): -10 | time.sleep(1) - | ^^^^^^^^^^ ASYNC220 - | - -ASYNC220.py:14:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods - | -13 | async def func(): -14 | subprocess.run("foo") - | ^^^^^^^^^^^^^^ ASYNC220 - | - -ASYNC220.py:18:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods - | -17 | async def func(): -18 | subprocess.call("foo") - | ^^^^^^^^^^^^^^^ ASYNC220 - | - -ASYNC220.py:26:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods - | -25 | async def func(): -26 | os.wait4(10) - | ^^^^^^^^ ASYNC220 - | - -ASYNC220.py:30:5: ASYNC220 Async functions should not call `time.sleep`, or `subprocess` methods - | -29 | async def func(): -30 | os.wait(12) - | ^^^^^^^ ASYNC220 - | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap deleted file mode 100644 index a3e23b483f628..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC222.py.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_async/mod.rs ---- -ASYNC222.py:5:5: ASYNC222 Async functions should not call synchronous `os` methods - | -4 | async def foo(): -5 | os.popen() - | ^^^^^^^^ ASYNC222 - | - -ASYNC222.py:9:5: ASYNC222 Async functions should not call synchronous `os` methods - | -8 | async def foo(): -9 | os.spawnl() - | ^^^^^^^^^ ASYNC222 - | From 80cd28712ae505f9fe23d7780d9c75ed9fa0dba4 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 01:53:27 -0400 Subject: [PATCH 14/22] remove references to flake8-trio --- README.md | 1 - docs/faq.md | 2 -- 2 files changed, 3 deletions(-) diff --git a/README.md b/README.md index 854d16a150dc4..ed891050da57a 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,6 @@ quality tools, including: - [flake8-super](https://pypi.org/project/flake8-super/) - [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/) - [flake8-todos](https://pypi.org/project/flake8-todos/) -- [flake8-trio](https://pypi.org/project/flake8-trio/) - [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) - [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) - [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102)) diff --git a/docs/faq.md b/docs/faq.md index b287b21c21cdf..f388bf7d08550 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -81,7 +81,6 @@ natively, including: - [flake8-super](https://pypi.org/project/flake8-super/) - [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/) - [flake8-todos](https://pypi.org/project/flake8-todos/) -- [flake8-trio](https://pypi.org/project/flake8-trio/) ([#8451](https://github.com/astral-sh/ruff/issues/8451)) - [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) - [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) - [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102)) @@ -194,7 +193,6 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [flake8-super](https://pypi.org/project/flake8-super/) - [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/) - [flake8-todos](https://pypi.org/project/flake8-todos/) -- [flake8-trio](https://pypi.org/project/flake8-trio/) ([#8451](https://github.com/astral-sh/ruff/issues/8451)) - [flake8-type-checking](https://pypi.org/project/flake8-type-checking/) - [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) - [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102)) From 4ed2eb06b790fd953e651261f739287d4a439084 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 08:59:16 -0400 Subject: [PATCH 15/22] move everything to a single match --- .../rules/blocking_process_invocation.rs | 126 +++++------------- 1 file changed, 32 insertions(+), 94 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index 934e558266161..bc0dd1bf1a29c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::analyze::typing::find_assigned_value; @@ -6,7 +6,7 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::registry::Rule; +use crate::registry::AsRule; /// ## What it does /// Checks that async functions do not create subprocesses with blocking methods. @@ -114,59 +114,39 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp return; } - if checker.enabled(Rule::CreateSubprocessInAsyncFunction) { - if is_subprocess_call(&call.func, checker.semantic()) - || (is_os_spawn(&call.func, checker.semantic()) && !is_p_wait(call, checker.semantic())) - { - checker.diagnostics.push(Diagnostic::new( - CreateSubprocessInAsyncFunction, - call.func.range(), - )); - } - } - - if checker.enabled(Rule::RunProcessInAsyncFunction) { - if is_run_process_call(&call.func, checker.semantic()) - || (is_os_spawn(&call.func, checker.semantic()) && is_p_wait(call, checker.semantic())) - { - checker.diagnostics.push(Diagnostic::new( - RunProcessInAsyncFunction, - call.func.range(), - )); - } - } - - if checker.enabled(Rule::WaitForProcessInAsyncFunction) { - if is_wait_for_process_call(&call.func, checker.semantic()) { - checker.diagnostics.push(Diagnostic::new( - WaitForProcessInAsyncFunction, - call.func.range(), - )); - } + let Some(diagnostic_kind) = + checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + .and_then(|qualified_name| match qualified_name.segments() { + ["subprocess", "Popen"] | ["os", "popen"] => { + Some(CreateSubprocessInAsyncFunction.into()) + } + ["os", "system" | "posix_spawn" | "posix_spawnp"] + | ["subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput" + | "getstatusoutput"] => Some(RunProcessInAsyncFunction.into()), + ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] | ["time", "sleep"] => { + Some(WaitForProcessInAsyncFunction.into()) + } + ["os", "spawnl" | "spawnle" | "spawnlp" | "spawnlpe" | "spawnv" | "spawnve" + | "spawnvp" | "spawnvpe"] => { + if is_p_wait(call, checker.semantic()) { + Some(RunProcessInAsyncFunction.into()) + } else { + Some(CreateSubprocessInAsyncFunction.into()) + } + } + _ => None, + }) + else { + return; + }; + let diagnostic = Diagnostic::new::(diagnostic_kind, call.func.range()); + if checker.enabled(diagnostic.kind.rule()) { + checker.diagnostics.push(diagnostic); } } -fn is_os_spawn(func: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - [ - "os", - "spawnl" - | "spawnle" - | "spawnlp" - | "spawnlpe" - | "spawnv" - | "spawnve" - | "spawnvp" - | "spawnvpe" - ] - ) - }) -} - fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool { let Some(arg) = call.arguments.find_argument("mode", 0) else { return true; @@ -184,45 +164,3 @@ fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool { } false } - -fn is_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["subprocess", "Popen"] | ["os", "popen"] - ) - }) -} - -fn is_run_process_call(func: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["os", "system" | "posix_spawn" | "posix_spawnp"] - | [ - "subprocess", - "run" - | "call" - | "check_call" - | "check_output" - | "getoutput" - | "getstatusoutput" - ] - ) - }) -} - -fn is_wait_for_process_call(func: &Expr, semantic: &SemanticModel) -> bool { - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] | ["time", "sleep"] - ) - }) -} From 0d48e8fe4b82aba848596a3bf91f797f3685fa88 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 09:20:01 -0400 Subject: [PATCH 16/22] move time.sleep check to its own rule --- .../test/fixtures/flake8_async/ASYNC22x.py | 5 - .../test/fixtures/flake8_async/ASYNC251.py | 14 + .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + .../ruff_linter/src/rules/flake8_async/mod.rs | 3 +- .../rules/blocking_process_invocation.rs | 2 +- .../flake8_async/rules/blocking_sleep.rs | 63 +++++ .../src/rules/flake8_async/rules/mod.rs | 2 + ...e8_async__tests__ASYNC220_ASYNC22x.py.snap | 86 +++--- ...e8_async__tests__ASYNC221_ASYNC22x.py.snap | 262 +++++++++--------- ...e8_async__tests__ASYNC222_ASYNC22x.py.snap | 93 +++---- ...e8_async__tests__ASYNC251_ASYNC251.py.snap | 9 + ruff.schema.json | 2 + 13 files changed, 314 insertions(+), 231 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py create mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC251_ASYNC251.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py index 21ffacd86d7a7..580e4439d0cef 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC22x.py @@ -1,14 +1,9 @@ import os import subprocess -import time # Violation cases: -async def func(): - time.sleep(1) # ASYNC222 - - async def func(): subprocess.run("foo") # ASYNC221 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py new file mode 100644 index 0000000000000..adc93ff8e3f3a --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC251.py @@ -0,0 +1,14 @@ +import time +import asyncio + + +async def func(): + time.sleep(1) # ASYNC251 + + +def func(): + time.sleep(1) # OK + + +async def func(): + asyncio.sleep(1) # OK diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index c6f9c6fe7e427..a86fdc2bb1bbe 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -515,6 +515,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { ]) { flake8_async::rules::blocking_process_invocation(checker, call); } + if checker.enabled(Rule::BlockingSleepInAsyncFunction) { + flake8_async::rules::blocking_sleep(checker, call); + } if checker.enabled(Rule::SleepForeverCall) { flake8_async::rules::sleep_forever_call(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index d73e553e3e50c..2e307871a28b7 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -303,6 +303,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Async, "221") => (RuleGroup::Preview, rules::flake8_async::rules::RunProcessInAsyncFunction), (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction), (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction), + (Flake8Async, "251") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingSleepInAsyncFunction), // flake8-builtins (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index 1faf94ea5aee9..70092042479a8 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -19,12 +19,13 @@ mod tests { #[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))] #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] + #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] #[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))] #[test_case(Rule::BlockingOpenCallInAsyncFunction, Path::new("ASYNC230.py"))] - #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] + #[test_case(Rule::BlockingSleepInAsyncFunction, Path::new("ASYNC251.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs index bc0dd1bf1a29c..bec9bf816c419 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_process_invocation.rs @@ -125,7 +125,7 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp ["os", "system" | "posix_spawn" | "posix_spawnp"] | ["subprocess", "run" | "call" | "check_call" | "check_output" | "getoutput" | "getstatusoutput"] => Some(RunProcessInAsyncFunction.into()), - ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] | ["time", "sleep"] => { + ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"] => { Some(WaitForProcessInAsyncFunction.into()) } ["os", "spawnl" | "spawnle" | "spawnlp" | "spawnlpe" | "spawnv" | "spawnve" diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs new file mode 100644 index 0000000000000..863a627a5e9b3 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs @@ -0,0 +1,63 @@ +use ruff_python_ast::ExprCall; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::name::QualifiedName; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks that async functions do not call `time.sleep`. +/// +/// ## Why is this bad? +/// Blocking an async function via a `time.sleep` call will block the entire +/// event loop, preventing it from executing other tasks while waiting for the +/// `time.sleep`, negating the benefits of asynchronous programming. +/// +/// Instead of `time.sleep`, use `asyncio.sleep`. +/// +/// ## Example +/// ```python +/// async def fetch(): +/// time.sleep(1) +/// ``` +/// +/// Use instead: +/// ```python +/// async def fetch(): +/// await asyncio.sleep(1) +/// ``` +#[violation] +pub struct BlockingSleepInAsyncFunction; + +impl Violation for BlockingSleepInAsyncFunction { + #[derive_message_formats] + fn message(&self) -> String { + format!("Async functions should not call `time.sleep`") + } +} + +fn is_blocking_sleep(qualified_name: &QualifiedName) -> bool { + matches!( + qualified_name.segments(), + ["time", "sleep"] + ) +} + +/// ASYNC251 +pub(crate) fn blocking_sleep(checker: &mut Checker, call: &ExprCall) { + if checker.semantic().in_async_context() { + if checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + .as_ref() + .is_some_and(is_blocking_sleep) + { + checker.diagnostics.push(Diagnostic::new( + BlockingSleepInAsyncFunction, + call.func.range(), + )); + } + } +} diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index 4686a3008ff90..d0d7a92780dcd 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -2,6 +2,7 @@ pub(crate) use async_function_with_timeout::*; pub(crate) use blocking_http_call::*; pub(crate) use blocking_open_call::*; pub(crate) use blocking_process_invocation::*; +pub(crate) use blocking_sleep::*; pub(crate) use sleep_forever_call::*; pub(crate) use sync_call::*; pub(crate) use timeout_without_await::*; @@ -13,6 +14,7 @@ mod blocking_http_call; mod blocking_open_call; mod blocking_process_invocation; mod sleep_forever_call; +mod blocking_sleep; mod sync_call; mod timeout_without_await; mod unneeded_sleep; diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap index ee54a873feeb4..e7db488fa6b54 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC220_ASYNC22x.py.snap @@ -1,77 +1,77 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC22x.py:36:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:31:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -34 | subprocess.getoutput() # ASYNC221 -35 | ) -36 | subprocess.Popen() # ASYNC220 +29 | subprocess.getoutput() # ASYNC221 +30 | ) +31 | subprocess.Popen() # ASYNC220 | ^^^^^^^^^^^^^^^^ ASYNC220 -37 | os.system() # ASYNC221 +32 | os.system() # ASYNC221 | -ASYNC22x.py:78:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:73:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -77 | # if mode is given, and is not os.P_WAIT: ASYNC220 -78 | os.spawnl(os.P_NOWAIT) # ASYNC220 +72 | # if mode is given, and is not os.P_WAIT: ASYNC220 +73 | os.spawnl(os.P_NOWAIT) # ASYNC220 | ^^^^^^^^^ ASYNC220 -79 | os.spawnl(P_NOWAIT) # ASYNC220 -80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +74 | os.spawnl(P_NOWAIT) # ASYNC220 +75 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 | -ASYNC22x.py:79:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:74:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -77 | # if mode is given, and is not os.P_WAIT: ASYNC220 -78 | os.spawnl(os.P_NOWAIT) # ASYNC220 -79 | os.spawnl(P_NOWAIT) # ASYNC220 +72 | # if mode is given, and is not os.P_WAIT: ASYNC220 +73 | os.spawnl(os.P_NOWAIT) # ASYNC220 +74 | os.spawnl(P_NOWAIT) # ASYNC220 | ^^^^^^^^^ ASYNC220 -80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 -81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 +75 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +76 | os.spawnl(mode=P_NOWAIT) # ASYNC220 | -ASYNC22x.py:80:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:75:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -78 | os.spawnl(os.P_NOWAIT) # ASYNC220 -79 | os.spawnl(P_NOWAIT) # ASYNC220 -80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +73 | os.spawnl(os.P_NOWAIT) # ASYNC220 +74 | os.spawnl(P_NOWAIT) # ASYNC220 +75 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 | ^^^^^^^^^ ASYNC220 -81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 +76 | os.spawnl(mode=P_NOWAIT) # ASYNC220 | -ASYNC22x.py:81:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:76:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -79 | os.spawnl(P_NOWAIT) # ASYNC220 -80 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 -81 | os.spawnl(mode=P_NOWAIT) # ASYNC220 +74 | os.spawnl(P_NOWAIT) # ASYNC220 +75 | os.spawnl(mode=os.P_NOWAIT) # ASYNC220 +76 | os.spawnl(mode=P_NOWAIT) # ASYNC220 | ^^^^^^^^^ ASYNC220 -82 | -83 | P_WAIT = os.P_WAIT +77 | +78 | P_WAIT = os.P_WAIT | -ASYNC22x.py:91:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:86:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -90 | # other weird cases: ASYNC220 -91 | os.spawnl(0) # ASYNC220 +85 | # other weird cases: ASYNC220 +86 | os.spawnl(0) # ASYNC220 | ^^^^^^^^^ ASYNC220 -92 | os.spawnl(1) # ASYNC220 -93 | os.spawnl(foo()) # ASYNC220 +87 | os.spawnl(1) # ASYNC220 +88 | os.spawnl(foo()) # ASYNC220 | -ASYNC22x.py:92:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:87:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -90 | # other weird cases: ASYNC220 -91 | os.spawnl(0) # ASYNC220 -92 | os.spawnl(1) # ASYNC220 +85 | # other weird cases: ASYNC220 +86 | os.spawnl(0) # ASYNC220 +87 | os.spawnl(1) # ASYNC220 | ^^^^^^^^^ ASYNC220 -93 | os.spawnl(foo()) # ASYNC220 +88 | os.spawnl(foo()) # ASYNC220 | -ASYNC22x.py:93:5: ASYNC220 Async functions should not create subprocesses with blocking methods +ASYNC22x.py:88:5: ASYNC220 Async functions should not create subprocesses with blocking methods | -91 | os.spawnl(0) # ASYNC220 -92 | os.spawnl(1) # ASYNC220 -93 | os.spawnl(foo()) # ASYNC220 +86 | os.spawnl(0) # ASYNC220 +87 | os.spawnl(1) # ASYNC220 +88 | os.spawnl(foo()) # ASYNC220 | ^^^^^^^^^ ASYNC220 -94 | -95 | # ASYNC222 +89 | +90 | # ASYNC222 | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap index d5fa49a3a0f11..59fa094dc38ae 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC221_ASYNC22x.py.snap @@ -1,226 +1,226 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC22x.py:13:5: ASYNC221 Async functions should not run processes with blocking methods - | -12 | async def func(): -13 | subprocess.run("foo") # ASYNC221 - | ^^^^^^^^^^^^^^ ASYNC221 - | +ASYNC22x.py:8:5: ASYNC221 Async functions should not run processes with blocking methods + | +7 | async def func(): +8 | subprocess.run("foo") # ASYNC221 + | ^^^^^^^^^^^^^^ ASYNC221 + | -ASYNC22x.py:17:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:12:5: ASYNC221 Async functions should not run processes with blocking methods | -16 | async def func(): -17 | subprocess.call("foo") # ASYNC221 +11 | async def func(): +12 | subprocess.call("foo") # ASYNC221 | ^^^^^^^^^^^^^^^ ASYNC221 | -ASYNC22x.py:34:9: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:29:9: ASYNC221 Async functions should not run processes with blocking methods | -32 | async def foo(): -33 | await async_fun( -34 | subprocess.getoutput() # ASYNC221 +27 | async def foo(): +28 | await async_fun( +29 | subprocess.getoutput() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 -35 | ) -36 | subprocess.Popen() # ASYNC220 +30 | ) +31 | subprocess.Popen() # ASYNC220 | -ASYNC22x.py:37:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:32:5: ASYNC221 Async functions should not run processes with blocking methods | -35 | ) -36 | subprocess.Popen() # ASYNC220 -37 | os.system() # ASYNC221 +30 | ) +31 | subprocess.Popen() # ASYNC220 +32 | os.system() # ASYNC221 | ^^^^^^^^^ ASYNC221 -38 | -39 | system() +33 | +34 | system() | -ASYNC22x.py:43:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:38:5: ASYNC221 Async functions should not run processes with blocking methods | -41 | os.anything() -42 | -43 | subprocess.run() # ASYNC221 +36 | os.anything() +37 | +38 | subprocess.run() # ASYNC221 | ^^^^^^^^^^^^^^ ASYNC221 -44 | subprocess.call() # ASYNC221 -45 | subprocess.check_call() # ASYNC221 +39 | subprocess.call() # ASYNC221 +40 | subprocess.check_call() # ASYNC221 | -ASYNC22x.py:44:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:39:5: ASYNC221 Async functions should not run processes with blocking methods | -43 | subprocess.run() # ASYNC221 -44 | subprocess.call() # ASYNC221 +38 | subprocess.run() # ASYNC221 +39 | subprocess.call() # ASYNC221 | ^^^^^^^^^^^^^^^ ASYNC221 -45 | subprocess.check_call() # ASYNC221 -46 | subprocess.check_output() # ASYNC221 +40 | subprocess.check_call() # ASYNC221 +41 | subprocess.check_output() # ASYNC221 | -ASYNC22x.py:45:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:40:5: ASYNC221 Async functions should not run processes with blocking methods | -43 | subprocess.run() # ASYNC221 -44 | subprocess.call() # ASYNC221 -45 | subprocess.check_call() # ASYNC221 +38 | subprocess.run() # ASYNC221 +39 | subprocess.call() # ASYNC221 +40 | subprocess.check_call() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^^ ASYNC221 -46 | subprocess.check_output() # ASYNC221 -47 | subprocess.getoutput() # ASYNC221 +41 | subprocess.check_output() # ASYNC221 +42 | subprocess.getoutput() # ASYNC221 | -ASYNC22x.py:46:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:41:5: ASYNC221 Async functions should not run processes with blocking methods | -44 | subprocess.call() # ASYNC221 -45 | subprocess.check_call() # ASYNC221 -46 | subprocess.check_output() # ASYNC221 +39 | subprocess.call() # ASYNC221 +40 | subprocess.check_call() # ASYNC221 +41 | subprocess.check_output() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^^^^ ASYNC221 -47 | subprocess.getoutput() # ASYNC221 -48 | subprocess.getstatusoutput() # ASYNC221 +42 | subprocess.getoutput() # ASYNC221 +43 | subprocess.getstatusoutput() # ASYNC221 | -ASYNC22x.py:47:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:42:5: ASYNC221 Async functions should not run processes with blocking methods | -45 | subprocess.check_call() # ASYNC221 -46 | subprocess.check_output() # ASYNC221 -47 | subprocess.getoutput() # ASYNC221 +40 | subprocess.check_call() # ASYNC221 +41 | subprocess.check_output() # ASYNC221 +42 | subprocess.getoutput() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 -48 | subprocess.getstatusoutput() # ASYNC221 +43 | subprocess.getstatusoutput() # ASYNC221 | -ASYNC22x.py:48:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:43:5: ASYNC221 Async functions should not run processes with blocking methods | -46 | subprocess.check_output() # ASYNC221 -47 | subprocess.getoutput() # ASYNC221 -48 | subprocess.getstatusoutput() # ASYNC221 +41 | subprocess.check_output() # ASYNC221 +42 | subprocess.getoutput() # ASYNC221 +43 | subprocess.getstatusoutput() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ASYNC221 -49 | -50 | await async_fun( +44 | +45 | await async_fun( | -ASYNC22x.py:51:9: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:46:9: ASYNC221 Async functions should not run processes with blocking methods | -50 | await async_fun( -51 | subprocess.getoutput() # ASYNC221 +45 | await async_fun( +46 | subprocess.getoutput() # ASYNC221 | ^^^^^^^^^^^^^^^^^^^^ ASYNC221 -52 | ) +47 | ) | -ASYNC22x.py:59:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:54:5: ASYNC221 Async functions should not run processes with blocking methods | -57 | subprocess() -58 | -59 | os.posix_spawn() # ASYNC221 +52 | subprocess() +53 | +54 | os.posix_spawn() # ASYNC221 | ^^^^^^^^^^^^^^ ASYNC221 -60 | os.posix_spawnp() # ASYNC221 +55 | os.posix_spawnp() # ASYNC221 | -ASYNC22x.py:60:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:55:5: ASYNC221 Async functions should not run processes with blocking methods | -59 | os.posix_spawn() # ASYNC221 -60 | os.posix_spawnp() # ASYNC221 +54 | os.posix_spawn() # ASYNC221 +55 | os.posix_spawnp() # ASYNC221 | ^^^^^^^^^^^^^^^ ASYNC221 -61 | -62 | os.spawn() +56 | +57 | os.spawn() | -ASYNC22x.py:66:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:61:5: ASYNC221 Async functions should not run processes with blocking methods | -64 | os.spawnllll() -65 | -66 | os.spawnl() # ASYNC221 +59 | os.spawnllll() +60 | +61 | os.spawnl() # ASYNC221 | ^^^^^^^^^ ASYNC221 -67 | os.spawnle() # ASYNC221 -68 | os.spawnlp() # ASYNC221 +62 | os.spawnle() # ASYNC221 +63 | os.spawnlp() # ASYNC221 | -ASYNC22x.py:67:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:62:5: ASYNC221 Async functions should not run processes with blocking methods | -66 | os.spawnl() # ASYNC221 -67 | os.spawnle() # ASYNC221 +61 | os.spawnl() # ASYNC221 +62 | os.spawnle() # ASYNC221 | ^^^^^^^^^^ ASYNC221 -68 | os.spawnlp() # ASYNC221 -69 | os.spawnlpe() # ASYNC221 +63 | os.spawnlp() # ASYNC221 +64 | os.spawnlpe() # ASYNC221 | -ASYNC22x.py:68:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:63:5: ASYNC221 Async functions should not run processes with blocking methods | -66 | os.spawnl() # ASYNC221 -67 | os.spawnle() # ASYNC221 -68 | os.spawnlp() # ASYNC221 +61 | os.spawnl() # ASYNC221 +62 | os.spawnle() # ASYNC221 +63 | os.spawnlp() # ASYNC221 | ^^^^^^^^^^ ASYNC221 -69 | os.spawnlpe() # ASYNC221 -70 | os.spawnv() # ASYNC221 +64 | os.spawnlpe() # ASYNC221 +65 | os.spawnv() # ASYNC221 | -ASYNC22x.py:69:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:64:5: ASYNC221 Async functions should not run processes with blocking methods | -67 | os.spawnle() # ASYNC221 -68 | os.spawnlp() # ASYNC221 -69 | os.spawnlpe() # ASYNC221 +62 | os.spawnle() # ASYNC221 +63 | os.spawnlp() # ASYNC221 +64 | os.spawnlpe() # ASYNC221 | ^^^^^^^^^^^ ASYNC221 -70 | os.spawnv() # ASYNC221 -71 | os.spawnve() # ASYNC221 +65 | os.spawnv() # ASYNC221 +66 | os.spawnve() # ASYNC221 | -ASYNC22x.py:70:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:65:5: ASYNC221 Async functions should not run processes with blocking methods | -68 | os.spawnlp() # ASYNC221 -69 | os.spawnlpe() # ASYNC221 -70 | os.spawnv() # ASYNC221 +63 | os.spawnlp() # ASYNC221 +64 | os.spawnlpe() # ASYNC221 +65 | os.spawnv() # ASYNC221 | ^^^^^^^^^ ASYNC221 -71 | os.spawnve() # ASYNC221 -72 | os.spawnvp() # ASYNC221 +66 | os.spawnve() # ASYNC221 +67 | os.spawnvp() # ASYNC221 | -ASYNC22x.py:71:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:66:5: ASYNC221 Async functions should not run processes with blocking methods | -69 | os.spawnlpe() # ASYNC221 -70 | os.spawnv() # ASYNC221 -71 | os.spawnve() # ASYNC221 +64 | os.spawnlpe() # ASYNC221 +65 | os.spawnv() # ASYNC221 +66 | os.spawnve() # ASYNC221 | ^^^^^^^^^^ ASYNC221 -72 | os.spawnvp() # ASYNC221 -73 | os.spawnvpe() # ASYNC221 +67 | os.spawnvp() # ASYNC221 +68 | os.spawnvpe() # ASYNC221 | -ASYNC22x.py:72:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:67:5: ASYNC221 Async functions should not run processes with blocking methods | -70 | os.spawnv() # ASYNC221 -71 | os.spawnve() # ASYNC221 -72 | os.spawnvp() # ASYNC221 +65 | os.spawnv() # ASYNC221 +66 | os.spawnve() # ASYNC221 +67 | os.spawnvp() # ASYNC221 | ^^^^^^^^^^ ASYNC221 -73 | os.spawnvpe() # ASYNC221 +68 | os.spawnvpe() # ASYNC221 | -ASYNC22x.py:73:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:68:5: ASYNC221 Async functions should not run processes with blocking methods | -71 | os.spawnve() # ASYNC221 -72 | os.spawnvp() # ASYNC221 -73 | os.spawnvpe() # ASYNC221 +66 | os.spawnve() # ASYNC221 +67 | os.spawnvp() # ASYNC221 +68 | os.spawnvpe() # ASYNC221 | ^^^^^^^^^^^ ASYNC221 -74 | -75 | P_NOWAIT = os.P_NOWAIT +69 | +70 | P_NOWAIT = os.P_NOWAIT | -ASYNC22x.py:86:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:81:5: ASYNC221 Async functions should not run processes with blocking methods | -85 | # if it is P_WAIT, ASYNC221 -86 | os.spawnl(P_WAIT) # ASYNC221 +80 | # if it is P_WAIT, ASYNC221 +81 | os.spawnl(P_WAIT) # ASYNC221 | ^^^^^^^^^ ASYNC221 -87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 -88 | os.spawnl(mode=P_WAIT) # ASYNC221 +82 | os.spawnl(mode=os.P_WAIT) # ASYNC221 +83 | os.spawnl(mode=P_WAIT) # ASYNC221 | -ASYNC22x.py:87:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:82:5: ASYNC221 Async functions should not run processes with blocking methods | -85 | # if it is P_WAIT, ASYNC221 -86 | os.spawnl(P_WAIT) # ASYNC221 -87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 +80 | # if it is P_WAIT, ASYNC221 +81 | os.spawnl(P_WAIT) # ASYNC221 +82 | os.spawnl(mode=os.P_WAIT) # ASYNC221 | ^^^^^^^^^ ASYNC221 -88 | os.spawnl(mode=P_WAIT) # ASYNC221 +83 | os.spawnl(mode=P_WAIT) # ASYNC221 | -ASYNC22x.py:88:5: ASYNC221 Async functions should not run processes with blocking methods +ASYNC22x.py:83:5: ASYNC221 Async functions should not run processes with blocking methods | -86 | os.spawnl(P_WAIT) # ASYNC221 -87 | os.spawnl(mode=os.P_WAIT) # ASYNC221 -88 | os.spawnl(mode=P_WAIT) # ASYNC221 +81 | os.spawnl(P_WAIT) # ASYNC221 +82 | os.spawnl(mode=os.P_WAIT) # ASYNC221 +83 | os.spawnl(mode=P_WAIT) # ASYNC221 | ^^^^^^^^^ ASYNC221 -89 | -90 | # other weird cases: ASYNC220 +84 | +85 | # other weird cases: ASYNC220 | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap index 3bdab672c7588..0c9e675ffa144 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC222_ASYNC22x.py.snap @@ -1,71 +1,64 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC22x.py:9:5: ASYNC222 Async functions should not wait on processes with blocking methods - | -8 | async def func(): -9 | time.sleep(1) # ASYNC222 - | ^^^^^^^^^^ ASYNC222 - | - -ASYNC22x.py:25:5: ASYNC222 Async functions should not wait on processes with blocking methods +ASYNC22x.py:20:5: ASYNC222 Async functions should not wait on processes with blocking methods | -24 | async def func(): -25 | os.wait4(10) # ASYNC222 +19 | async def func(): +20 | os.wait4(10) # ASYNC222 | ^^^^^^^^ ASYNC222 | -ASYNC22x.py:29:5: ASYNC222 Async functions should not wait on processes with blocking methods +ASYNC22x.py:24:5: ASYNC222 Async functions should not wait on processes with blocking methods | -28 | async def func(): -29 | os.wait(12) # ASYNC222 +23 | async def func(): +24 | os.wait(12) # ASYNC222 | ^^^^^^^ ASYNC222 | -ASYNC22x.py:96:5: ASYNC222 Async functions should not wait on processes with blocking methods +ASYNC22x.py:91:5: ASYNC222 Async functions should not wait on processes with blocking methods | -95 | # ASYNC222 -96 | os.wait() # ASYNC222 +90 | # ASYNC222 +91 | os.wait() # ASYNC222 | ^^^^^^^ ASYNC222 -97 | os.wait3() # ASYNC222 -98 | os.wait4() # ASYNC222 +92 | os.wait3() # ASYNC222 +93 | os.wait4() # ASYNC222 | -ASYNC22x.py:97:5: ASYNC222 Async functions should not wait on processes with blocking methods +ASYNC22x.py:92:5: ASYNC222 Async functions should not wait on processes with blocking methods | -95 | # ASYNC222 -96 | os.wait() # ASYNC222 -97 | os.wait3() # ASYNC222 +90 | # ASYNC222 +91 | os.wait() # ASYNC222 +92 | os.wait3() # ASYNC222 | ^^^^^^^^ ASYNC222 -98 | os.wait4() # ASYNC222 -99 | os.waitid() # ASYNC222 +93 | os.wait4() # ASYNC222 +94 | os.waitid() # ASYNC222 | -ASYNC22x.py:98:5: ASYNC222 Async functions should not wait on processes with blocking methods - | - 96 | os.wait() # ASYNC222 - 97 | os.wait3() # ASYNC222 - 98 | os.wait4() # ASYNC222 - | ^^^^^^^^ ASYNC222 - 99 | os.waitid() # ASYNC222 -100 | os.waitpid() # ASYNC222 - | +ASYNC22x.py:93:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +91 | os.wait() # ASYNC222 +92 | os.wait3() # ASYNC222 +93 | os.wait4() # ASYNC222 + | ^^^^^^^^ ASYNC222 +94 | os.waitid() # ASYNC222 +95 | os.waitpid() # ASYNC222 + | -ASYNC22x.py:99:5: ASYNC222 Async functions should not wait on processes with blocking methods - | - 97 | os.wait3() # ASYNC222 - 98 | os.wait4() # ASYNC222 - 99 | os.waitid() # ASYNC222 - | ^^^^^^^^^ ASYNC222 -100 | os.waitpid() # ASYNC222 - | +ASYNC22x.py:94:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +92 | os.wait3() # ASYNC222 +93 | os.wait4() # ASYNC222 +94 | os.waitid() # ASYNC222 + | ^^^^^^^^^ ASYNC222 +95 | os.waitpid() # ASYNC222 + | -ASYNC22x.py:100:5: ASYNC222 Async functions should not wait on processes with blocking methods - | - 98 | os.wait4() # ASYNC222 - 99 | os.waitid() # ASYNC222 -100 | os.waitpid() # ASYNC222 - | ^^^^^^^^^^ ASYNC222 -101 | -102 | os.waitpi() - | +ASYNC22x.py:95:5: ASYNC222 Async functions should not wait on processes with blocking methods + | +93 | os.wait4() # ASYNC222 +94 | os.waitid() # ASYNC222 +95 | os.waitpid() # ASYNC222 + | ^^^^^^^^^^ ASYNC222 +96 | +97 | os.waitpi() + | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC251_ASYNC251.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC251_ASYNC251.py.snap new file mode 100644 index 0000000000000..ffafdf9d99cd4 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC251_ASYNC251.py.snap @@ -0,0 +1,9 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC251.py:6:5: ASYNC251 Async functions should not call `time.sleep` + | +5 | async def func(): +6 | time.sleep(1) # ASYNC251 + | ^^^^^^^^^^ ASYNC251 + | diff --git a/ruff.schema.json b/ruff.schema.json index eadcb725445c8..0e9d0aa30870b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2708,6 +2708,8 @@ "ASYNC222", "ASYNC23", "ASYNC230", + "ASYNC25", + "ASYNC251", "B", "B0", "B00", From 2f44f7a589582f6e1dc42f77fd584a154ec33121 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 09:23:43 -0400 Subject: [PATCH 17/22] format --- .../src/rules/flake8_async/rules/blocking_sleep.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs index 863a627a5e9b3..e1a118ee213a1 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_sleep.rs @@ -39,10 +39,7 @@ impl Violation for BlockingSleepInAsyncFunction { } fn is_blocking_sleep(qualified_name: &QualifiedName) -> bool { - matches!( - qualified_name.segments(), - ["time", "sleep"] - ) + matches!(qualified_name.segments(), ["time", "sleep"]) } /// ASYNC251 From 33a960a86ed109f7162c543d943fd961ad2f8c67 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 15 Mar 2024 17:50:33 -0400 Subject: [PATCH 18/22] add redirects for TRIO -> ASYNC --- crates/ruff_linter/src/rule_redirects.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/ruff_linter/src/rule_redirects.rs b/crates/ruff_linter/src/rule_redirects.rs index 4e74fca8936b2..98a001b7832fd 100644 --- a/crates/ruff_linter/src/rule_redirects.rs +++ b/crates/ruff_linter/src/rule_redirects.rs @@ -103,6 +103,16 @@ static REDIRECTS: Lazy> = Lazy::new(|| { ("TRY200", "B904"), ("PGH001", "S307"), ("PGH002", "G010"), + // flake8-trio and flake8-async merged with name flake8-async + ("TRIO", "ASYNC"), + ("TRIO1", "ASYNC1"), + ("TRIO10", "ASYNC10"), + ("TRIO100", "ASYNC100"), + ("TRIO105", "ASYNC105"), + ("TRIO109", "ASYNC109"), + ("TRIO11", "ASYNC11"), + ("TRIO110", "ASYNC110"), + ("TRIO115", "ASYNC115"), // Removed in v0.5 ("PLR1701", "SIM101"), // Test redirect by exact code From 406594324aa3bd5f5b7c4c0f5cc2597024275bb2 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Mon, 18 Mar 2024 19:19:19 -0400 Subject: [PATCH 19/22] move requested rules to stable --- crates/ruff_linter/src/codes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 2e307871a28b7..61e81adb372d8 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -300,10 +300,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall), (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction), - (Flake8Async, "221") => (RuleGroup::Preview, rules::flake8_async::rules::RunProcessInAsyncFunction), + (Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction), (Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction), (Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction), - (Flake8Async, "251") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingSleepInAsyncFunction), + (Flake8Async, "251") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingSleepInAsyncFunction), // flake8-builtins (Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing), From b0aba0ecb502a7ab886d1d0b4fa24ce7d69e2603 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 25 Jun 2024 14:17:55 +0200 Subject: [PATCH 20/22] cargo fmt --- crates/ruff_linter/src/rules/flake8_async/rules/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index d0d7a92780dcd..1a1950c21a72c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -13,8 +13,8 @@ mod async_function_with_timeout; mod blocking_http_call; mod blocking_open_call; mod blocking_process_invocation; -mod sleep_forever_call; mod blocking_sleep; +mod sleep_forever_call; mod sync_call; mod timeout_without_await; mod unneeded_sleep; From b1340b42a7b1f7e03b5b92b763320500c6044677 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 25 Jun 2024 15:50:14 +0200 Subject: [PATCH 21/22] Update rule code comments --- .../src/rules/flake8_async/rules/async_function_with_timeout.rs | 2 +- crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs | 2 +- .../src/rules/flake8_async/rules/timeout_without_await.rs | 2 +- .../ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs | 2 +- .../ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs index c73c51acca8db..3b7ae6f73882f 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_function_with_timeout.rs @@ -40,7 +40,7 @@ impl Violation for TrioAsyncFunctionWithTimeout { } } -/// TRIO109 +/// ASYNC109 pub(crate) fn async_function_with_timeout( checker: &mut Checker, function_def: &ast::StmtFunctionDef, diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs index 383658042b5dc..cccf7fc20bd91 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/sync_call.rs @@ -50,7 +50,7 @@ impl Violation for TrioSyncCall { } } -/// TRIO105 +/// ASYNC105 pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) { if !checker.semantic().seen_module(Modules::TRIO) { return; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs b/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs index 67e6d26ba3d5f..f60b2002d4871 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs @@ -44,7 +44,7 @@ impl Violation for TrioTimeoutWithoutAwait { } } -/// TRIO100 +/// ASYNC100 pub(crate) fn timeout_without_await( checker: &mut Checker, with_stmt: &StmtWith, diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs index 921e0adaa9ea2..aded4e23d1a75 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/unneeded_sleep.rs @@ -41,7 +41,7 @@ impl Violation for TrioUnneededSleep { } } -/// TRIO110 +/// ASYNC110 pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile) { if !checker.semantic().seen_module(Modules::TRIO) { return; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs index f8ddca3364c03..f1d23f618e289 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs @@ -45,7 +45,7 @@ impl AlwaysFixableViolation for TrioZeroSleepCall { } } -/// TRIO115 +/// ASYNC115 pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) { if !checker.semantic().seen_module(Modules::TRIO) { return; From d3d93d7d816c4c567b442bce745f348afa5f0dff Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 26 Jun 2024 09:49:21 +0200 Subject: [PATCH 22/22] Remap `TRIO` to `ASYNC1` to closer match existing behavior --- crates/ruff_linter/src/rule_redirects.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rule_redirects.rs b/crates/ruff_linter/src/rule_redirects.rs index 98a001b7832fd..2f174e80d009a 100644 --- a/crates/ruff_linter/src/rule_redirects.rs +++ b/crates/ruff_linter/src/rule_redirects.rs @@ -104,7 +104,7 @@ static REDIRECTS: Lazy> = Lazy::new(|| { ("PGH001", "S307"), ("PGH002", "G010"), // flake8-trio and flake8-async merged with name flake8-async - ("TRIO", "ASYNC"), + ("TRIO", "ASYNC1"), ("TRIO1", "ASYNC1"), ("TRIO10", "ASYNC10"), ("TRIO100", "ASYNC100"),