mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 01:51:30 +00:00
[TRIO
] Add TRIO105
: SyncTrioCall
(#8490)
## Summary Adds `TRIO105` from the [flake8-trio plugin](https://github.com/Zac-HD/flake8-trio). The `MethodName` logic mirrors that of `TRIO100` to stay consistent within the plugin. It is at 95% parity with the exception of upstream also checking for a slightly more complex scenario where a call to `start()` on a `trio.Nursery` context should also be immediately awaited. Upstream plugin appears to just check for anything named `nursery` judging from [the relevant issue](https://github.com/Zac-HD/flake8-trio/issues/56). Unsure if we want to do so something similar or, alternatively, if there is some capability in ruff to check for calls made on this context some other way ## Test Plan Added a new fixture, based on [the one from upstream plugin](https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio105.py) ## Issue link Refers: https://github.com/astral-sh/ruff/issues/8451
This commit is contained in:
parent
72ebde8d38
commit
4170ef0508
11 changed files with 872 additions and 61 deletions
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
async def func() -> None:
|
||||||
|
trio.run(foo) # OK, not async
|
||||||
|
|
||||||
|
# OK
|
||||||
|
await trio.aclose_forcefully(foo)
|
||||||
|
await trio.open_file(foo)
|
||||||
|
await trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
await trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
await trio.open_tcp_listeners(foo)
|
||||||
|
await trio.open_tcp_stream(foo, foo)
|
||||||
|
await trio.open_unix_socket(foo)
|
||||||
|
await trio.run_process(foo)
|
||||||
|
await trio.sleep(5)
|
||||||
|
await trio.sleep_until(5)
|
||||||
|
await trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
await trio.lowlevel.checkpoint()
|
||||||
|
await trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
await trio.lowlevel.open_process(foo)
|
||||||
|
await trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
await trio.lowlevel.wait_readable(foo)
|
||||||
|
await trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
await trio.lowlevel.wait_writable(foo)
|
||||||
|
|
||||||
|
# TRIO105
|
||||||
|
trio.aclose_forcefully(foo)
|
||||||
|
trio.open_file(foo)
|
||||||
|
trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
trio.open_tcp_listeners(foo)
|
||||||
|
trio.open_tcp_stream(foo, foo)
|
||||||
|
trio.open_unix_socket(foo)
|
||||||
|
trio.run_process(foo)
|
||||||
|
trio.serve_listeners(foo, foo)
|
||||||
|
trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
trio.serve_tcp(foo, foo)
|
||||||
|
trio.sleep(foo)
|
||||||
|
trio.sleep_forever()
|
||||||
|
trio.sleep_until(foo)
|
||||||
|
trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
trio.lowlevel.checkpoint()
|
||||||
|
trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
trio.lowlevel.open_process()
|
||||||
|
trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
trio.lowlevel.wait_readable(foo)
|
||||||
|
trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
trio.lowlevel.wait_writable(foo)
|
||||||
|
|
||||||
|
async with await trio.open_file(foo): # Ok
|
||||||
|
pass
|
||||||
|
|
||||||
|
async with trio.open_file(foo): # TRIO105
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def func() -> None:
|
||||||
|
# TRIO105 (without fix)
|
||||||
|
trio.open_file(foo)
|
|
@ -15,8 +15,8 @@ use crate::rules::{
|
||||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
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_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||||
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
|
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
|
||||||
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||||
};
|
};
|
||||||
use crate::settings::types::PythonVersion;
|
use crate::settings::types::PythonVersion;
|
||||||
|
|
||||||
|
@ -926,6 +926,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::ImplicitCwd) {
|
if checker.enabled(Rule::ImplicitCwd) {
|
||||||
refurb::rules::no_implicit_cwd(checker, call);
|
refurb::rules::no_implicit_cwd(checker, call);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::TrioSyncCall) {
|
||||||
|
flake8_trio::rules::sync_call(checker, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Dict(
|
Expr::Dict(
|
||||||
dict @ ast::ExprDict {
|
dict @ ast::ExprDict {
|
||||||
|
|
|
@ -292,6 +292,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
|
|
||||||
// flake8-trio
|
// flake8-trio
|
||||||
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
|
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
|
||||||
|
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
|
||||||
|
|
||||||
// flake8-builtins
|
// flake8-builtins
|
||||||
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
|
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
|
||||||
|
|
157
crates/ruff_linter/src/rules/flake8_trio/method_name.rs
Normal file
157
crates/ruff_linter/src/rules/flake8_trio/method_name.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use ruff_python_ast::call_path::CallPath;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub(super) enum MethodName {
|
||||||
|
AcloseForcefully,
|
||||||
|
CancelScope,
|
||||||
|
CancelShieldedCheckpoint,
|
||||||
|
Checkpoint,
|
||||||
|
CheckpointIfCancelled,
|
||||||
|
FailAfter,
|
||||||
|
FailAt,
|
||||||
|
MoveOnAfter,
|
||||||
|
MoveOnAt,
|
||||||
|
OpenFile,
|
||||||
|
OpenProcess,
|
||||||
|
OpenSslOverTcpListeners,
|
||||||
|
OpenSslOverTcpStream,
|
||||||
|
OpenTcpListeners,
|
||||||
|
OpenTcpStream,
|
||||||
|
OpenUnixSocket,
|
||||||
|
PermanentlyDetachCoroutineObject,
|
||||||
|
ReattachDetachedCoroutineObject,
|
||||||
|
RunProcess,
|
||||||
|
ServeListeners,
|
||||||
|
ServeSslOverTcp,
|
||||||
|
ServeTcp,
|
||||||
|
Sleep,
|
||||||
|
SleepForever,
|
||||||
|
TemporarilyDetachCoroutineObject,
|
||||||
|
WaitReadable,
|
||||||
|
WaitTaskRescheduled,
|
||||||
|
WaitWritable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodName {
|
||||||
|
/// Returns `true` if the method is async, `false` if it is sync.
|
||||||
|
pub(super) fn is_async(self) -> bool {
|
||||||
|
match self {
|
||||||
|
MethodName::AcloseForcefully
|
||||||
|
| MethodName::CancelShieldedCheckpoint
|
||||||
|
| MethodName::Checkpoint
|
||||||
|
| MethodName::CheckpointIfCancelled
|
||||||
|
| MethodName::OpenFile
|
||||||
|
| MethodName::OpenProcess
|
||||||
|
| MethodName::OpenSslOverTcpListeners
|
||||||
|
| MethodName::OpenSslOverTcpStream
|
||||||
|
| MethodName::OpenTcpListeners
|
||||||
|
| MethodName::OpenTcpStream
|
||||||
|
| MethodName::OpenUnixSocket
|
||||||
|
| MethodName::PermanentlyDetachCoroutineObject
|
||||||
|
| MethodName::ReattachDetachedCoroutineObject
|
||||||
|
| MethodName::RunProcess
|
||||||
|
| MethodName::ServeListeners
|
||||||
|
| MethodName::ServeSslOverTcp
|
||||||
|
| MethodName::ServeTcp
|
||||||
|
| MethodName::Sleep
|
||||||
|
| MethodName::SleepForever
|
||||||
|
| MethodName::TemporarilyDetachCoroutineObject
|
||||||
|
| MethodName::WaitReadable
|
||||||
|
| MethodName::WaitTaskRescheduled
|
||||||
|
| MethodName::WaitWritable => true,
|
||||||
|
|
||||||
|
MethodName::MoveOnAfter
|
||||||
|
| MethodName::MoveOnAt
|
||||||
|
| MethodName::FailAfter
|
||||||
|
| MethodName::FailAt
|
||||||
|
| MethodName::CancelScope => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodName {
|
||||||
|
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
|
||||||
|
match call_path.as_slice() {
|
||||||
|
["trio", "CancelScope"] => Some(Self::CancelScope),
|
||||||
|
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
|
||||||
|
["trio", "fail_after"] => Some(Self::FailAfter),
|
||||||
|
["trio", "fail_at"] => Some(Self::FailAt),
|
||||||
|
["trio", "lowlevel", "cancel_shielded_checkpoint"] => {
|
||||||
|
Some(Self::CancelShieldedCheckpoint)
|
||||||
|
}
|
||||||
|
["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint),
|
||||||
|
["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled),
|
||||||
|
["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess),
|
||||||
|
["trio", "lowlevel", "permanently_detach_coroutine_object"] => {
|
||||||
|
Some(Self::PermanentlyDetachCoroutineObject)
|
||||||
|
}
|
||||||
|
["trio", "lowlevel", "reattach_detached_coroutine_object"] => {
|
||||||
|
Some(Self::ReattachDetachedCoroutineObject)
|
||||||
|
}
|
||||||
|
["trio", "lowlevel", "temporarily_detach_coroutine_object"] => {
|
||||||
|
Some(Self::TemporarilyDetachCoroutineObject)
|
||||||
|
}
|
||||||
|
["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable),
|
||||||
|
["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled),
|
||||||
|
["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable),
|
||||||
|
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
|
||||||
|
["trio", "move_on_at"] => Some(Self::MoveOnAt),
|
||||||
|
["trio", "open_file"] => Some(Self::OpenFile),
|
||||||
|
["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners),
|
||||||
|
["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream),
|
||||||
|
["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners),
|
||||||
|
["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream),
|
||||||
|
["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket),
|
||||||
|
["trio", "run_process"] => Some(Self::RunProcess),
|
||||||
|
["trio", "serve_listeners"] => Some(Self::ServeListeners),
|
||||||
|
["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp),
|
||||||
|
["trio", "serve_tcp"] => Some(Self::ServeTcp),
|
||||||
|
["trio", "sleep"] => Some(Self::Sleep),
|
||||||
|
["trio", "sleep_forever"] => Some(Self::SleepForever),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for MethodName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"),
|
||||||
|
MethodName::CancelScope => write!(f, "trio.CancelScope"),
|
||||||
|
MethodName::CancelShieldedCheckpoint => {
|
||||||
|
write!(f, "trio.lowlevel.cancel_shielded_checkpoint")
|
||||||
|
}
|
||||||
|
MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"),
|
||||||
|
MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"),
|
||||||
|
MethodName::FailAfter => write!(f, "trio.fail_after"),
|
||||||
|
MethodName::FailAt => write!(f, "trio.fail_at"),
|
||||||
|
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
|
||||||
|
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
|
||||||
|
MethodName::OpenFile => write!(f, "trio.open_file"),
|
||||||
|
MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"),
|
||||||
|
MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"),
|
||||||
|
MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"),
|
||||||
|
MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"),
|
||||||
|
MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"),
|
||||||
|
MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"),
|
||||||
|
MethodName::PermanentlyDetachCoroutineObject => {
|
||||||
|
write!(f, "trio.lowlevel.permanently_detach_coroutine_object")
|
||||||
|
}
|
||||||
|
MethodName::ReattachDetachedCoroutineObject => {
|
||||||
|
write!(f, "trio.lowlevel.reattach_detached_coroutine_object")
|
||||||
|
}
|
||||||
|
MethodName::RunProcess => write!(f, "trio.run_process"),
|
||||||
|
MethodName::ServeListeners => write!(f, "trio.serve_listeners"),
|
||||||
|
MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"),
|
||||||
|
MethodName::ServeTcp => write!(f, "trio.serve_tcp"),
|
||||||
|
MethodName::Sleep => write!(f, "trio.sleep"),
|
||||||
|
MethodName::SleepForever => write!(f, "trio.sleep_forever"),
|
||||||
|
MethodName::TemporarilyDetachCoroutineObject => {
|
||||||
|
write!(f, "trio.lowlevel.temporarily_detach_coroutine_object")
|
||||||
|
}
|
||||||
|
MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"),
|
||||||
|
MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"),
|
||||||
|
MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/).
|
//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/).
|
||||||
|
pub(super) mod method_name;
|
||||||
pub(crate) mod rules;
|
pub(crate) mod rules;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -14,6 +15,7 @@ mod tests {
|
||||||
use crate::test::test_path;
|
use crate::test::test_path;
|
||||||
|
|
||||||
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
|
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
|
||||||
|
#[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub(crate) use sync_call::*;
|
||||||
pub(crate) use timeout_without_await::*;
|
pub(crate) use timeout_without_await::*;
|
||||||
|
|
||||||
|
mod sync_call;
|
||||||
mod timeout_without_await;
|
mod timeout_without_await;
|
||||||
|
|
87
crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs
Normal file
87
crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::{Expr, ExprCall};
|
||||||
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::fix::edits::pad;
|
||||||
|
use crate::rules::flake8_trio::method_name::MethodName;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for calls to trio functions that are not immediately awaited.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Many of the functions exposed by trio are asynchronous, and must be awaited
|
||||||
|
/// to take effect. Calling a trio function without an `await` can lead to
|
||||||
|
/// `RuntimeWarning` diagnostics and unexpected behaviour.
|
||||||
|
///
|
||||||
|
/// ## Fix safety
|
||||||
|
/// This rule's fix is marked as unsafe, as adding an `await` to a function
|
||||||
|
/// call changes its semantics and runtime behavior.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// async def double_sleep(x):
|
||||||
|
/// trio.sleep(2 * x)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// async def double_sleep(x):
|
||||||
|
/// await trio.sleep(2 * x)
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct TrioSyncCall {
|
||||||
|
method_name: MethodName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for TrioSyncCall {
|
||||||
|
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||||
|
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let Self { method_name } = self;
|
||||||
|
format!("Call to `{method_name}` is not immediately awaited")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> Option<String> {
|
||||||
|
Some(format!("Add `await`"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRIO105
|
||||||
|
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
|
let Some(method_name) = ({
|
||||||
|
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
MethodName::try_from(&call_path)
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !method_name.is_async() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker
|
||||||
|
.semantic()
|
||||||
|
.current_expression_parent()
|
||||||
|
.is_some_and(Expr::is_await_expr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic = Diagnostic::new(TrioSyncCall { method_name }, call.range);
|
||||||
|
if checker.semantic().in_async_context() {
|
||||||
|
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||||
|
pad(
|
||||||
|
"await".to_string(),
|
||||||
|
TextRange::new(call.func.start(), call.func.start()),
|
||||||
|
checker.locator(),
|
||||||
|
),
|
||||||
|
call.func.start(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
use ruff_python_ast::helpers::AwaitVisitor;
|
||||||
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{Expr, ExprAwait, Stmt, StmtWith, WithItem};
|
use ruff_python_ast::{StmtWith, WithItem};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_trio::method_name::MethodName;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for trio functions that should contain await but don't.
|
/// Checks for trio functions that should contain await but don't.
|
||||||
|
@ -56,6 +57,17 @@ pub(crate) fn timeout_without_await(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !matches!(
|
||||||
|
method_name,
|
||||||
|
MethodName::MoveOnAfter
|
||||||
|
| MethodName::MoveOnAt
|
||||||
|
| MethodName::FailAfter
|
||||||
|
| MethodName::FailAt
|
||||||
|
| MethodName::CancelScope
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut visitor = AwaitVisitor::default();
|
let mut visitor = AwaitVisitor::default();
|
||||||
visitor.visit_body(&with_stmt.body);
|
visitor.visit_body(&with_stmt.body);
|
||||||
if visitor.seen_await {
|
if visitor.seen_await {
|
||||||
|
@ -67,59 +79,3 @@ pub(crate) fn timeout_without_await(
|
||||||
with_stmt.range,
|
with_stmt.range,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
enum MethodName {
|
|
||||||
MoveOnAfter,
|
|
||||||
MoveOnAt,
|
|
||||||
FailAfter,
|
|
||||||
FailAt,
|
|
||||||
CancelScope,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MethodName {
|
|
||||||
fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
|
|
||||||
match call_path.as_slice() {
|
|
||||||
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
|
|
||||||
["trio", "move_on_at"] => Some(Self::MoveOnAt),
|
|
||||||
["trio", "fail_after"] => Some(Self::FailAfter),
|
|
||||||
["trio", "fail_at"] => Some(Self::FailAt),
|
|
||||||
["trio", "CancelScope"] => Some(Self::CancelScope),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for MethodName {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
|
|
||||||
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
|
|
||||||
MethodName::FailAfter => write!(f, "trio.fail_after"),
|
|
||||||
MethodName::FailAt => write!(f, "trio.fail_at"),
|
|
||||||
MethodName::CancelScope => write!(f, "trio.CancelScope"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct AwaitVisitor {
|
|
||||||
seen_await: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitor<'_> for AwaitVisitor {
|
|
||||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
|
||||||
match stmt {
|
|
||||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
|
|
||||||
_ => walk_stmt(self, stmt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &Expr) {
|
|
||||||
if let Expr::Await(ExprAwait { .. }) = expr {
|
|
||||||
self.seen_await = true;
|
|
||||||
} else {
|
|
||||||
walk_expr(self, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,514 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
|
||||||
|
---
|
||||||
|
TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
||||||
|
|
|
||||||
|
29 | # TRIO105
|
||||||
|
30 | trio.aclose_forcefully(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
31 | trio.open_file(foo)
|
||||||
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
27 27 | await trio.lowlevel.wait_writable(foo)
|
||||||
|
28 28 |
|
||||||
|
29 29 | # TRIO105
|
||||||
|
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
|
||||||
|
|
|
||||||
|
29 | # TRIO105
|
||||||
|
30 | trio.aclose_forcefully(foo)
|
||||||
|
31 | trio.open_file(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
28 28 |
|
||||||
|
29 29 | # TRIO105
|
||||||
|
30 30 | trio.aclose_forcefully(foo)
|
||||||
|
31 |- trio.open_file(foo)
|
||||||
|
31 |+ await trio.open_file(foo)
|
||||||
|
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
30 | trio.aclose_forcefully(foo)
|
||||||
|
31 | trio.open_file(foo)
|
||||||
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
34 | trio.open_tcp_listeners(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
29 29 | # TRIO105
|
||||||
|
30 30 | trio.aclose_forcefully(foo)
|
||||||
|
31 31 | trio.open_file(foo)
|
||||||
|
32 |- trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
32 |+ await trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
31 | trio.open_file(foo)
|
||||||
|
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
30 30 | trio.aclose_forcefully(foo)
|
||||||
|
31 31 | trio.open_file(foo)
|
||||||
|
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
33 |- trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
33 |+ await trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
34 34 | trio.open_tcp_listeners(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
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
|
||||||
|
35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 | trio.open_unix_socket(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
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)
|
||||||
|
34 |- trio.open_tcp_listeners(foo)
|
||||||
|
34 |+ await trio.open_tcp_listeners(foo)
|
||||||
|
35 35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
36 | trio.open_unix_socket(foo)
|
||||||
|
37 | trio.run_process(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||||
|
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
34 34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 |- trio.open_tcp_stream(foo, foo)
|
||||||
|
35 |+ await trio.open_tcp_stream(foo, foo)
|
||||||
|
36 36 | trio.open_unix_socket(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 | trio.open_unix_socket(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
37 | trio.run_process(foo)
|
||||||
|
38 | trio.serve_listeners(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||||
|
34 34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 |- trio.open_unix_socket(foo)
|
||||||
|
36 |+ await trio.open_unix_socket(foo)
|
||||||
|
37 37 | trio.run_process(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 | trio.open_unix_socket(foo)
|
||||||
|
37 | trio.run_process(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
38 | trio.serve_listeners(foo, foo)
|
||||||
|
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
34 34 | trio.open_tcp_listeners(foo)
|
||||||
|
35 35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 36 | trio.open_unix_socket(foo)
|
||||||
|
37 |- trio.run_process(foo)
|
||||||
|
37 |+ await trio.run_process(foo)
|
||||||
|
38 38 | trio.serve_listeners(foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
36 | trio.open_unix_socket(foo)
|
||||||
|
37 | trio.run_process(foo)
|
||||||
|
38 | trio.serve_listeners(foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
40 | trio.serve_tcp(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
35 35 | trio.open_tcp_stream(foo, foo)
|
||||||
|
36 36 | trio.open_unix_socket(foo)
|
||||||
|
37 37 | trio.run_process(foo)
|
||||||
|
38 |- trio.serve_listeners(foo, foo)
|
||||||
|
38 |+ await trio.serve_listeners(foo, foo)
|
||||||
|
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
37 | trio.run_process(foo)
|
||||||
|
38 | trio.serve_listeners(foo, foo)
|
||||||
|
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
40 | trio.serve_tcp(foo, foo)
|
||||||
|
41 | trio.sleep(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
36 36 | trio.open_unix_socket(foo)
|
||||||
|
37 37 | trio.run_process(foo)
|
||||||
|
38 38 | trio.serve_listeners(foo, foo)
|
||||||
|
39 |- trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
39 |+ await trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
40 40 | trio.serve_tcp(foo, foo)
|
||||||
|
41 41 | trio.sleep(foo)
|
||||||
|
42 42 | trio.sleep_forever()
|
||||||
|
|
||||||
|
TRIO105.py:40:5: TRIO105 [*] 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
|
||||||
|
41 | trio.sleep(foo)
|
||||||
|
42 | trio.sleep_forever()
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
37 37 | trio.run_process(foo)
|
||||||
|
38 38 | trio.serve_listeners(foo, foo)
|
||||||
|
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
40 |- trio.serve_tcp(foo, foo)
|
||||||
|
40 |+ await trio.serve_tcp(foo, foo)
|
||||||
|
41 41 | trio.sleep(foo)
|
||||||
|
42 42 | trio.sleep_forever()
|
||||||
|
43 43 | trio.sleep_until(foo)
|
||||||
|
|
||||||
|
TRIO105.py:41:5: TRIO105 [*] 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
|
||||||
|
42 | trio.sleep_forever()
|
||||||
|
43 | trio.sleep_until(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
38 38 | trio.serve_listeners(foo, foo)
|
||||||
|
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
40 40 | trio.serve_tcp(foo, foo)
|
||||||
|
41 |- trio.sleep(foo)
|
||||||
|
41 |+ await trio.sleep(foo)
|
||||||
|
42 42 | trio.sleep_forever()
|
||||||
|
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
|
||||||
|
|
|
||||||
|
40 | trio.serve_tcp(foo, foo)
|
||||||
|
41 | trio.sleep(foo)
|
||||||
|
42 | trio.sleep_forever()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
43 | trio.sleep_until(foo)
|
||||||
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||||
|
40 40 | trio.serve_tcp(foo, foo)
|
||||||
|
41 41 | trio.sleep(foo)
|
||||||
|
42 |- trio.sleep_forever()
|
||||||
|
42 |+ await trio.sleep_forever()
|
||||||
|
43 43 | trio.sleep_until(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
42 | trio.sleep_forever()
|
||||||
|
43 | trio.sleep_until(foo)
|
||||||
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
45 | trio.lowlevel.checkpoint()
|
||||||
|
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
41 41 | trio.sleep(foo)
|
||||||
|
42 42 | trio.sleep_forever()
|
||||||
|
43 43 | trio.sleep_until(foo)
|
||||||
|
44 |- trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
44 |+ await trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 45 | trio.lowlevel.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
|
||||||
|
|
|
||||||
|
43 | trio.sleep_until(foo)
|
||||||
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 | trio.lowlevel.checkpoint()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 | trio.lowlevel.open_process()
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
42 42 | trio.sleep_forever()
|
||||||
|
43 43 | trio.sleep_until(foo)
|
||||||
|
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 |- trio.lowlevel.checkpoint()
|
||||||
|
45 |+ await trio.lowlevel.checkpoint()
|
||||||
|
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
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
|
||||||
|
|
|
||||||
|
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 | trio.lowlevel.checkpoint()
|
||||||
|
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
47 | trio.lowlevel.open_process()
|
||||||
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
43 43 | trio.sleep_until(foo)
|
||||||
|
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 45 | trio.lowlevel.checkpoint()
|
||||||
|
46 |- trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
46 |+ await trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 47 | trio.lowlevel.open_process()
|
||||||
|
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
|
||||||
|
|
|
||||||
|
45 | trio.lowlevel.checkpoint()
|
||||||
|
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 | trio.lowlevel.open_process()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||||
|
45 45 | trio.lowlevel.checkpoint()
|
||||||
|
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 |- trio.lowlevel.open_process()
|
||||||
|
47 |+ await trio.lowlevel.open_process()
|
||||||
|
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 | trio.lowlevel.open_process()
|
||||||
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
45 45 | trio.lowlevel.checkpoint()
|
||||||
|
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 47 | trio.lowlevel.open_process()
|
||||||
|
48 |- trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
48 |+ await trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
47 | trio.lowlevel.open_process()
|
||||||
|
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||||
|
47 47 | trio.lowlevel.open_process()
|
||||||
|
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 |- trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
49 |+ await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
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
|
||||||
|
51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
47 47 | trio.lowlevel.open_process()
|
||||||
|
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 |- trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
50 |+ await trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
53 | trio.lowlevel.wait_writable(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||||
|
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 |- trio.lowlevel.wait_readable(foo)
|
||||||
|
51 |+ await trio.lowlevel.wait_readable(foo)
|
||||||
|
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
53 | trio.lowlevel.wait_writable(foo)
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||||
|
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
52 |- trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
52 |+ await trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
53 53 | trio.lowlevel.wait_writable(foo)
|
||||||
|
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
|
||||||
|
|
|
||||||
|
51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
53 | trio.lowlevel.wait_writable(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
54 |
|
||||||
|
55 | async with await trio.open_file(foo): # Ok
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||||
|
51 51 | trio.lowlevel.wait_readable(foo)
|
||||||
|
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||||
|
53 |- trio.lowlevel.wait_writable(foo)
|
||||||
|
53 |+ await trio.lowlevel.wait_writable(foo)
|
||||||
|
54 54 |
|
||||||
|
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
|
||||||
|
|
|
||||||
|
56 | pass
|
||||||
|
57 |
|
||||||
|
58 | async with trio.open_file(foo): # TRIO105
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
59 | pass
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
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
|
||||||
|
59 59 | pass
|
||||||
|
60 60 |
|
||||||
|
61 61 |
|
||||||
|
|
||||||
|
TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited
|
||||||
|
|
|
||||||
|
62 | def func() -> None:
|
||||||
|
63 | # TRIO105 (without fix)
|
||||||
|
64 | trio.open_file(foo)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||||
|
|
|
||||||
|
= help: Add `await`
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||||
use crate::call_path::CallPath;
|
use crate::call_path::CallPath;
|
||||||
use crate::parenthesize::parenthesized_range;
|
use crate::parenthesize::parenthesized_range;
|
||||||
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
|
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
|
||||||
|
use crate::visitor::Visitor;
|
||||||
use crate::AnyNodeRef;
|
use crate::AnyNodeRef;
|
||||||
use crate::{
|
use crate::{
|
||||||
self as ast, Arguments, CmpOp, ExceptHandler, Expr, MatchCase, Pattern, Stmt, TypeParam,
|
self as ast, Arguments, CmpOp, ExceptHandler, Expr, MatchCase, Pattern, Stmt, TypeParam,
|
||||||
|
@ -931,6 +932,29 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`Visitor`] that detects the presence of `await` expressions in the current scope.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AwaitVisitor {
|
||||||
|
pub seen_await: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visitor<'_> for AwaitVisitor {
|
||||||
|
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||||
|
match stmt {
|
||||||
|
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
|
||||||
|
_ => crate::visitor::walk_stmt(self, stmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, expr: &Expr) {
|
||||||
|
if let Expr::Await(ast::ExprAwait { .. }) = expr {
|
||||||
|
self.seen_await = true;
|
||||||
|
} else {
|
||||||
|
crate::visitor::walk_expr(self, expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if a `Stmt` is a docstring.
|
/// Return `true` if a `Stmt` is a docstring.
|
||||||
pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
|
pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
|
||||||
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
||||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3472,6 +3472,7 @@
|
||||||
"TRIO1",
|
"TRIO1",
|
||||||
"TRIO10",
|
"TRIO10",
|
||||||
"TRIO100",
|
"TRIO100",
|
||||||
|
"TRIO105",
|
||||||
"TRY",
|
"TRY",
|
||||||
"TRY0",
|
"TRY0",
|
||||||
"TRY00",
|
"TRY00",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue