Skip to content

Commit

Permalink
[TRIO] Add TRIO109 rule (#8534)
Browse files Browse the repository at this point in the history
## Summary

Adds TRIO109 from the [flake8-trio
plugin](https://github.com/Zac-HD/flake8-trio).
Relates to: #8451
  • Loading branch information
karpetrosyan committed Nov 7, 2023
1 parent 621e98f commit e2c7b1e
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 0 deletions.
10 changes: 10 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
async def func():
...


async def func(timeout):
...


async def func(timeout=10):
...
3 changes: 3 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
}
}
if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) {
flake8_trio::rules::async_function_with_timeout(checker, function_def);
}
#[cfg(feature = "unreachable-code")]
if checker.enabled(Rule::UnreachableCode) {
checker
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-trio
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
(Flake8Trio, "109") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
(Flake8Trio, "110") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioUnneededSleep),
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),

Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/flake8_trio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod tests {

#[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"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;

/// ## What it does
/// Checks for `async` functions with a `timeout` argument.
///
/// ## Why is this bad?
/// Rather than implementing asynchronous timeout behavior manually, prefer
/// trio's built-in timeout functionality, available as `trio.fail_after`,
/// `trio.move_on_after`, `trio.fail_at`, and `trio.move_on_at`.
///
/// ## Example
/// ```python
/// async def func():
/// await long_running_task(timeout=2)
/// ```
///
/// Use instead:
/// ```python
/// async def func():
/// with trio.fail_after(2):
/// await long_running_task()
/// ```
#[violation]
pub struct TrioAsyncFunctionWithTimeout;

impl Violation for TrioAsyncFunctionWithTimeout {
#[derive_message_formats]
fn message(&self) -> String {
format!("Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior")
}
}

/// TRIO109
pub(crate) fn async_function_with_timeout(
checker: &mut Checker,
function_def: &ast::StmtFunctionDef,
) {
if !function_def.is_async {
return;
}
let Some(timeout) = function_def.parameters.find("timeout") else {
return;
};
checker.diagnostics.push(Diagnostic::new(
TrioAsyncFunctionWithTimeout,
timeout.range(),
));
}
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
---
TRIO109.py:5:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior
|
5 | async def func(timeout):
| ^^^^^^^ TRIO109
6 | ...
|

TRIO109.py:9:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior
|
9 | async def func(timeout=10):
| ^^^^^^^^^^ TRIO109
10 | ...
|


9 changes: 9 additions & 0 deletions crates/ruff_python_ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2281,6 +2281,15 @@ pub struct Parameters {
}

impl Parameters {
/// Returns the [`ParameterWithDefault`] with the given name, or `None` if no such [`ParameterWithDefault`] exists.
pub fn find(&self, name: &str) -> Option<&ParameterWithDefault> {
self.posonlyargs
.iter()
.chain(&self.args)
.chain(&self.kwonlyargs)
.find(|arg| arg.parameter.name.as_str() == name)
}

/// Returns `true` if a parameter with the given name included in this [`Parameters`].
pub fn includes(&self, name: &str) -> bool {
if self
Expand Down
1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e2c7b1e

Please sign in to comment.