diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py index b15b028048..6ec5d0133e 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py @@ -165,3 +165,15 @@ async def test_trio_async115_helpers(): await trio.sleep(seconds=0) # ASYNC115 await trio.sleep(delay=0) # OK + +# https://github.com/astral-sh/ruff/issues/18740 +# The autofix for this is unsafe due to the comments. +async def func(): + import trio + + await ( + trio # comment + .sleep( # comment + 0 # comment + ) + ) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index 1921af7412..ec4a70d266 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -1,3 +1,4 @@ +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; use ruff_python_semantic::Modules; @@ -32,6 +33,21 @@ use crate::{AlwaysFixableViolation, Edit, Fix}; /// async def func(): /// await trio.lowlevel.checkpoint() /// ``` +/// ## Fix safety +/// This rule's fix is marked as unsafe if there's comments in the +/// `trio.sleep(0)` expression, as comments may be removed. +/// +/// For example, the fix would be marked as unsafe in the following case: +/// ```python +/// import trio +/// +/// +/// async def func(): +/// await trio.sleep( # comment +/// # comment +/// 0 +/// ) +/// ``` #[derive(ViolationMetadata)] pub(crate) struct AsyncZeroSleep { module: AsyncModule, @@ -119,7 +135,15 @@ pub(crate) fn async_zero_sleep(checker: &Checker, call: &ExprCall) { let reference_edit = Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); - Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) + Ok(Fix::applicable_edits( + import_edit, + [reference_edit, arg_edit], + if checker.comment_ranges().intersects(call.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )) }); } } diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap index 5bd34508d7..18c6ea8dac 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC115_ASYNC115.py.snap @@ -252,3 +252,28 @@ ASYNC115.py:166:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `tr 166 |- await trio.sleep(seconds=0) # ASYNC115 166 |+ await trio.lowlevel.checkpoint() # ASYNC115 167 167 | await trio.sleep(delay=0) # OK +168 168 | +169 169 | # https://github.com/astral-sh/ruff/issues/18740 + +ASYNC115.py:175:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +174 | await ( +175 | / trio # comment +176 | | .sleep( # comment +177 | | 0 # comment +178 | | ) + | |_____^ ASYNC115 +179 | ) + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Unsafe fix +172 172 | import trio +173 173 | +174 174 | await ( +175 |- trio # comment +176 |- .sleep( # comment +177 |- 0 # comment +178 |- ) + 175 |+ trio.lowlevel.checkpoint() +179 176 | )