mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
[ruff
] Check for non-context-manager use of pytest.raises
, pytest.warns
, and pytest.deprecated_call
(RUF061
) (#17368)
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> This PR aims to close #16605. ## Summary This PR introduces a new rule (`RUF061`) that detects non-contextmanager usage of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call`. This pattern is discouraged and [was proposed in flake8-pytest-style](https://github.com/m-burst/flake8-pytest-style/pull/332), but the corresponding PR has been open for over a month without activity. Additionally, this PR provides an unsafe fix for simple cases where the non-contextmanager form can be transformed into the context manager form. Examples of supported patterns are listed in `RUF061_raises.py`, `RUF061_warns.py`, and `RUF061_deprecated_call.py` test files. The more complex case from the original issue (involving two separate statements): ```python excinfo = pytest.raises(ValueError, int, "hello") assert excinfo.match("^invalid literal") ``` is getting fixed like this: ```python with pytest.raises(ValueError) as excinfo: int("hello") assert excinfo.match("^invalid literal") ``` Putting match in the raises call requires multi-statement transformation, which I am not sure how to implement. ## Test Plan <!-- How was it tested? --> New test files were added to cover various usages of the non-contextmanager form of pytest.raises, warns, and deprecated_call.
This commit is contained in:
parent
c5b58187da
commit
c3aa965546
12 changed files with 635 additions and 0 deletions
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_deprecation_warning(s):
|
||||
warnings.warn(s, DeprecationWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.deprecated_call():
|
||||
raise_deprecation_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning))
|
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
import pytest
|
||||
|
||||
|
||||
def func(a, b):
|
||||
return a / b
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_ok_as():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_match():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_kwargs():
|
||||
pytest.raises(func=func, expected_exception=ZeroDivisionError)
|
||||
|
||||
|
||||
def test_error_multi_statement():
|
||||
excinfo = pytest.raises(ValueError, int, "hello")
|
||||
assert excinfo.match("^invalid literal")
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.raises(ZeroDivisionError, lambda: 1 / 0)
|
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_user_warning(s):
|
||||
warnings.warn(s, UserWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.warns(UserWarning):
|
||||
raise_user_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning))
|
|
@ -975,6 +975,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||
]) {
|
||||
flake8_pytest_style::rules::raises_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::LegacyFormPytestRaises) {
|
||||
ruff::rules::legacy_raises_warns_deprecated_call(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[Rule::PytestWarnsWithoutWarning, Rule::PytestWarnsTooBroad]) {
|
||||
flake8_pytest_style::rules::warns_call(checker, call);
|
||||
}
|
||||
|
|
|
@ -1027,6 +1027,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip),
|
||||
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
|
||||
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),
|
||||
|
|
|
@ -100,6 +100,9 @@ mod tests {
|
|||
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_2.py"))]
|
||||
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_3.py"))]
|
||||
#[test_case(Rule::InEmptyCollection, Path::new("RUF060.py"))]
|
||||
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_raises.py"))]
|
||||
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_warns.py"))]
|
||||
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_deprecated_call.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
|
||||
#[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))]
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
use itertools::{Either, Itertools};
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, AtomicNodeIndex, Expr, Stmt, StmtExpr, StmtWith, WithItem};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_trivia::{has_leading_content, has_trailing_content, leading_indentation};
|
||||
use ruff_source_file::UniversalNewlines;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::fmt;
|
||||
|
||||
use crate::{FixAvailability, Violation, checkers::ast::Checker};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for non-contextmanager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The context-manager form is more readable, easier to extend, and supports additional kwargs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// excinfo = pytest.raises(ValueError, int, "hello")
|
||||
/// pytest.warns(UserWarning, my_function, arg)
|
||||
/// pytest.deprecated_call(my_deprecated_function, arg1, arg2)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// with pytest.raises(ValueError) as excinfo:
|
||||
/// int("hello")
|
||||
/// with pytest.warns(UserWarning):
|
||||
/// my_function(arg)
|
||||
/// with pytest.deprecated_call():
|
||||
/// my_deprecated_function(arg1, arg2)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||
/// - [`pytest` documentation: `pytest.warns`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-warns)
|
||||
/// - [`pytest` documentation: `pytest.deprecated_call`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-deprecated-call)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct LegacyFormPytestRaises {
|
||||
context_type: PytestContextType,
|
||||
}
|
||||
|
||||
impl Violation for LegacyFormPytestRaises {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Use context-manager form of `pytest.{}()`",
|
||||
self.context_type
|
||||
)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(format!(
|
||||
"Use `pytest.{}()` as a context-manager",
|
||||
self.context_type
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum representing the type of pytest context manager
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum PytestContextType {
|
||||
Raises,
|
||||
Warns,
|
||||
DeprecatedCall,
|
||||
}
|
||||
|
||||
impl fmt::Display for PytestContextType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::Raises => "raises",
|
||||
Self::Warns => "warns",
|
||||
Self::DeprecatedCall => "deprecated_call",
|
||||
};
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
impl PytestContextType {
|
||||
fn from_expr_name(func: &Expr, semantic: &SemanticModel) -> Option<Self> {
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.and_then(|qualified_name| match qualified_name.segments() {
|
||||
["pytest", "raises"] => Some(Self::Raises),
|
||||
["pytest", "warns"] => Some(Self::Warns),
|
||||
["pytest", "deprecated_call"] => Some(Self::DeprecatedCall),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_arg(self) -> Option<(&'static str, usize)> {
|
||||
match self {
|
||||
Self::Raises => Some(("expected_exception", 0)),
|
||||
Self::Warns => Some(("expected_warning", 0)),
|
||||
Self::DeprecatedCall => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn func_arg(self) -> (&'static str, usize) {
|
||||
match self {
|
||||
Self::Raises | Self::Warns => ("func", 1),
|
||||
Self::DeprecatedCall => ("func", 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn legacy_raises_warns_deprecated_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
let Some(context_type) = PytestContextType::from_expr_name(&call.func, semantic) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (func_arg_name, func_arg_position) = context_type.func_arg();
|
||||
if call
|
||||
.arguments
|
||||
.find_argument(func_arg_name, func_arg_position)
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(LegacyFormPytestRaises { context_type }, call.range());
|
||||
|
||||
let stmt = semantic.current_statement();
|
||||
if !has_leading_content(stmt.start(), checker.source())
|
||||
&& !has_trailing_content(stmt.end(), checker.source())
|
||||
{
|
||||
if let Some(with_stmt) = try_fix_legacy_call(context_type, stmt, semantic) {
|
||||
let generated = checker.generator().stmt(&Stmt::With(with_stmt));
|
||||
let first_line = checker.locator().line_str(stmt.start());
|
||||
let indentation = leading_indentation(first_line);
|
||||
let mut indented = String::new();
|
||||
for (idx, line) in generated.universal_newlines().enumerate() {
|
||||
if idx == 0 {
|
||||
indented.push_str(&line);
|
||||
} else {
|
||||
indented.push_str(checker.stylist().line_ending().as_str());
|
||||
indented.push_str(indentation);
|
||||
indented.push_str(&line);
|
||||
}
|
||||
}
|
||||
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
indented,
|
||||
stmt.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fix_legacy_call(
|
||||
context_type: PytestContextType,
|
||||
stmt: &Stmt,
|
||||
semantic: &SemanticModel,
|
||||
) -> Option<StmtWith> {
|
||||
match stmt {
|
||||
Stmt::Expr(StmtExpr { value, .. }) => {
|
||||
let call = value.as_call_expr()?;
|
||||
|
||||
// Handle two patterns for legacy calls:
|
||||
// 1. Direct usage: `pytest.raises(ZeroDivisionError, func, 1, b=0)`
|
||||
// 2. With match method: `pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")`
|
||||
//
|
||||
// The second branch specifically looks for raises().match() pattern which only exists for
|
||||
// `raises` (not `warns`/`deprecated_call`) since only `raises` returns an ExceptionInfo
|
||||
// object with a .match() method. We need to preserve this match condition when converting
|
||||
// to context manager form.
|
||||
if PytestContextType::from_expr_name(&call.func, semantic) == Some(context_type) {
|
||||
generate_with_statement(context_type, call, None, None, None)
|
||||
} else if let PytestContextType::Raises = context_type {
|
||||
let inner_raises_call = call
|
||||
.func
|
||||
.as_attribute_expr()
|
||||
.filter(|expr_attribute| &expr_attribute.attr == "match")
|
||||
.and_then(|expr_attribute| expr_attribute.value.as_call_expr())
|
||||
.filter(|inner_call| {
|
||||
PytestContextType::from_expr_name(&inner_call.func, semantic)
|
||||
== Some(PytestContextType::Raises)
|
||||
})?;
|
||||
let match_arg = call.arguments.args.first();
|
||||
generate_with_statement(context_type, inner_raises_call, match_arg, None, None)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
|
||||
let call = value.as_call_expr().filter(|call| {
|
||||
PytestContextType::from_expr_name(&call.func, semantic) == Some(context_type)
|
||||
})?;
|
||||
let (optional_vars, assign_targets) = match context_type {
|
||||
PytestContextType::Raises => {
|
||||
let [target] = targets.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
(Some(target), None)
|
||||
}
|
||||
PytestContextType::Warns | PytestContextType::DeprecatedCall => {
|
||||
(None, Some(targets.as_slice()))
|
||||
}
|
||||
};
|
||||
|
||||
generate_with_statement(context_type, call, None, optional_vars, assign_targets)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_with_statement(
|
||||
context_type: PytestContextType,
|
||||
legacy_call: &ast::ExprCall,
|
||||
match_arg: Option<&Expr>,
|
||||
optional_vars: Option<&Expr>,
|
||||
assign_targets: Option<&[Expr]>,
|
||||
) -> Option<StmtWith> {
|
||||
let expected = if let Some((name, position)) = context_type.expected_arg() {
|
||||
Some(legacy_call.arguments.find_argument_value(name, position)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (func_arg_name, func_arg_position) = context_type.func_arg();
|
||||
let func = legacy_call
|
||||
.arguments
|
||||
.find_argument_value(func_arg_name, func_arg_position)?;
|
||||
|
||||
let (func_args, func_keywords): (Vec<_>, Vec<_>) = legacy_call
|
||||
.arguments
|
||||
.arguments_source_order()
|
||||
.skip(if expected.is_some() { 2 } else { 1 })
|
||||
.partition_map(|arg_or_keyword| match arg_or_keyword {
|
||||
ast::ArgOrKeyword::Arg(expr) => Either::Left(expr.clone()),
|
||||
ast::ArgOrKeyword::Keyword(keyword) => Either::Right(keyword.clone()),
|
||||
});
|
||||
|
||||
let context_call = ast::ExprCall {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
func: legacy_call.func.clone(),
|
||||
arguments: ast::Arguments {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
args: expected.cloned().as_slice().into(),
|
||||
keywords: match_arg
|
||||
.map(|expr| ast::Keyword {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
// Take range from the original expression so that the keyword
|
||||
// argument is generated after positional arguments
|
||||
range: expr.range(),
|
||||
arg: Some(ast::Identifier::new("match", TextRange::default())),
|
||||
value: expr.clone(),
|
||||
})
|
||||
.as_slice()
|
||||
.into(),
|
||||
},
|
||||
};
|
||||
|
||||
let func_call = ast::ExprCall {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
func: Box::new(func.clone()),
|
||||
arguments: ast::Arguments {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
args: func_args.into(),
|
||||
keywords: func_keywords.into(),
|
||||
},
|
||||
};
|
||||
|
||||
let body = if let Some(assign_targets) = assign_targets {
|
||||
Stmt::Assign(ast::StmtAssign {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
targets: assign_targets.to_vec(),
|
||||
value: Box::new(func_call.into()),
|
||||
})
|
||||
} else {
|
||||
Stmt::Expr(StmtExpr {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
value: Box::new(func_call.into()),
|
||||
})
|
||||
};
|
||||
|
||||
Some(StmtWith {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
is_async: false,
|
||||
items: vec![WithItem {
|
||||
node_index: AtomicNodeIndex::dummy(),
|
||||
range: TextRange::default(),
|
||||
context_expr: context_call.into(),
|
||||
optional_vars: optional_vars.map(|var| Box::new(var.clone())),
|
||||
}],
|
||||
body: vec![body],
|
||||
})
|
||||
}
|
|
@ -21,6 +21,7 @@ pub(crate) use invalid_formatter_suppression_comment::*;
|
|||
pub(crate) use invalid_index_type::*;
|
||||
pub(crate) use invalid_pyproject_toml::*;
|
||||
pub(crate) use invalid_rule_code::*;
|
||||
pub(crate) use legacy_form_pytest_raises::*;
|
||||
pub(crate) use map_int_version_parsing::*;
|
||||
pub(crate) use missing_fstring_syntax::*;
|
||||
pub(crate) use mutable_class_default::*;
|
||||
|
@ -82,6 +83,7 @@ mod invalid_formatter_suppression_comment;
|
|||
mod invalid_index_type;
|
||||
mod invalid_pyproject_toml;
|
||||
mod invalid_rule_code;
|
||||
mod legacy_form_pytest_raises;
|
||||
mod map_int_version_parsing;
|
||||
mod missing_fstring_syntax;
|
||||
mod mutable_class_default;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF061_deprecated_call.py:16:5: RUF061 [*] Use context-manager form of `pytest.deprecated_call()`
|
||||
|
|
||||
15 | def test_error_trivial():
|
||||
16 | pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.deprecated_call()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | def test_error_trivial():
|
||||
16 |- pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
16 |+ with pytest.deprecated_call():
|
||||
17 |+ raise_deprecation_warning("deprecated")
|
||||
17 18 |
|
||||
18 19 |
|
||||
19 20 | def test_error_assign():
|
||||
|
||||
RUF061_deprecated_call.py:20:9: RUF061 [*] Use context-manager form of `pytest.deprecated_call()`
|
||||
|
|
||||
19 | def test_error_assign():
|
||||
20 | s = pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
21 | print(s)
|
||||
|
|
||||
= help: Use `pytest.deprecated_call()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | def test_error_assign():
|
||||
20 |- s = pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
20 |+ with pytest.deprecated_call():
|
||||
21 |+ s = raise_deprecation_warning("deprecated")
|
||||
21 22 | print(s)
|
||||
22 23 |
|
||||
23 24 |
|
||||
|
||||
RUF061_deprecated_call.py:25:5: RUF061 [*] Use context-manager form of `pytest.deprecated_call()`
|
||||
|
|
||||
24 | def test_error_lambda():
|
||||
25 | pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.deprecated_call()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 |
|
||||
23 23 |
|
||||
24 24 | def test_error_lambda():
|
||||
25 |- pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning))
|
||||
25 |+ with pytest.deprecated_call():
|
||||
26 |+ (lambda: warnings.warn("", DeprecationWarning))()
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF061_raises.py:19:5: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
18 | def test_error_trivial():
|
||||
19 | pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 18 | def test_error_trivial():
|
||||
19 |- pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
19 |+ with pytest.raises(ZeroDivisionError):
|
||||
20 |+ func(1, b=0)
|
||||
20 21 |
|
||||
21 22 |
|
||||
22 23 | def test_error_match():
|
||||
|
||||
RUF061_raises.py:23:5: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
22 | def test_error_match():
|
||||
23 | pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
20 20 |
|
||||
21 21 |
|
||||
22 22 | def test_error_match():
|
||||
23 |- pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")
|
||||
23 |+ with pytest.raises(ZeroDivisionError, match="division by zero"):
|
||||
24 |+ func(1, b=0)
|
||||
24 25 |
|
||||
25 26 |
|
||||
26 27 | def test_error_assign():
|
||||
|
||||
RUF061_raises.py:27:15: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
26 | def test_error_assign():
|
||||
27 | excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
24 24 |
|
||||
25 25 |
|
||||
26 26 | def test_error_assign():
|
||||
27 |- excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
27 |+ with pytest.raises(ZeroDivisionError) as excinfo:
|
||||
28 |+ func(1, b=0)
|
||||
28 29 |
|
||||
29 30 |
|
||||
30 31 | def test_error_kwargs():
|
||||
|
||||
RUF061_raises.py:31:5: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
30 | def test_error_kwargs():
|
||||
31 | pytest.raises(func=func, expected_exception=ZeroDivisionError)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
28 28 |
|
||||
29 29 |
|
||||
30 30 | def test_error_kwargs():
|
||||
31 |- pytest.raises(func=func, expected_exception=ZeroDivisionError)
|
||||
31 |+ with pytest.raises(ZeroDivisionError):
|
||||
32 |+ func()
|
||||
32 33 |
|
||||
33 34 |
|
||||
34 35 | def test_error_multi_statement():
|
||||
|
||||
RUF061_raises.py:35:15: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
34 | def test_error_multi_statement():
|
||||
35 | excinfo = pytest.raises(ValueError, int, "hello")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
36 | assert excinfo.match("^invalid literal")
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
32 32 |
|
||||
33 33 |
|
||||
34 34 | def test_error_multi_statement():
|
||||
35 |- excinfo = pytest.raises(ValueError, int, "hello")
|
||||
35 |+ with pytest.raises(ValueError) as excinfo:
|
||||
36 |+ int("hello")
|
||||
36 37 | assert excinfo.match("^invalid literal")
|
||||
37 38 |
|
||||
38 39 |
|
||||
|
||||
RUF061_raises.py:40:5: RUF061 [*] Use context-manager form of `pytest.raises()`
|
||||
|
|
||||
39 | def test_error_lambda():
|
||||
40 | pytest.raises(ZeroDivisionError, lambda: 1 / 0)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.raises()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 39 | def test_error_lambda():
|
||||
40 |- pytest.raises(ZeroDivisionError, lambda: 1 / 0)
|
||||
40 |+ with pytest.raises(ZeroDivisionError):
|
||||
41 |+ (lambda: 1 / 0)()
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF061_warns.py:16:5: RUF061 [*] Use context-manager form of `pytest.warns()`
|
||||
|
|
||||
15 | def test_error_trivial():
|
||||
16 | pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.warns()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | def test_error_trivial():
|
||||
16 |- pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
16 |+ with pytest.warns(UserWarning):
|
||||
17 |+ raise_user_warning("warning")
|
||||
17 18 |
|
||||
18 19 |
|
||||
19 20 | def test_error_assign():
|
||||
|
||||
RUF061_warns.py:20:9: RUF061 [*] Use context-manager form of `pytest.warns()`
|
||||
|
|
||||
19 | def test_error_assign():
|
||||
20 | s = pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
21 | print(s)
|
||||
|
|
||||
= help: Use `pytest.warns()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | def test_error_assign():
|
||||
20 |- s = pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
20 |+ with pytest.warns(UserWarning):
|
||||
21 |+ s = raise_user_warning("warning")
|
||||
21 22 | print(s)
|
||||
22 23 |
|
||||
23 24 |
|
||||
|
||||
RUF061_warns.py:25:5: RUF061 [*] Use context-manager form of `pytest.warns()`
|
||||
|
|
||||
24 | def test_error_lambda():
|
||||
25 | pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF061
|
||||
|
|
||||
= help: Use `pytest.warns()` as a context-manager
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 |
|
||||
23 23 |
|
||||
24 24 | def test_error_lambda():
|
||||
25 |- pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning))
|
||||
25 |+ with pytest.warns(UserWarning):
|
||||
26 |+ (lambda: warnings.warn("", UserWarning))()
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -4039,6 +4039,7 @@
|
|||
"RUF059",
|
||||
"RUF06",
|
||||
"RUF060",
|
||||
"RUF061",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue