mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
[pycodestyle
] Exempt pytest.importorskip()
calls (E402
) (#14474)
## Summary Resolves #13537. ## Test Plan `cargo nextest run` and `cargo insta test`.
This commit is contained in:
parent
6a4d207db7
commit
b9c53a74f9
5 changed files with 41 additions and 3 deletions
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_3.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_3.py
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
import pytest
|
||||
|
||||
pytest.importorskip("foo.bar")
|
||||
|
||||
import re
|
||||
from sys import version
|
||||
|
||||
from numpy import *
|
|
@ -453,6 +453,8 @@ impl<'a> Checker<'a> {
|
|||
|
||||
impl<'a> Visitor<'a> for Checker<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
let in_preview = self.settings.preview.is_enabled();
|
||||
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_node(stmt);
|
||||
|
||||
|
@ -504,7 +506,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
|||
|| helpers::in_nested_block(self.semantic.current_statements())
|
||||
|| imports::is_matplotlib_activation(stmt, self.semantic())
|
||||
|| imports::is_sys_path_modification(stmt, self.semantic())
|
||||
|| imports::is_os_environ_modification(stmt, self.semantic()))
|
||||
|| imports::is_os_environ_modification(stmt, self.semantic())
|
||||
|| (in_preview && imports::is_pytest_importorskip(stmt, self.semantic())))
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
|
|
|
@ -72,14 +72,15 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_3.py"))]
|
||||
#[test_case(Rule::RedundantBackslash, Path::new("E502.py"))]
|
||||
// E741 has different behaviour for `.pyi` files in preview mode
|
||||
#[test_case(Rule::AmbiguousVariableName, Path::new("E741.pyi"))]
|
||||
#[test_case(Rule::TooManyNewlinesAtEndOfFile, Path::new("W391_0.py"))]
|
||||
#[test_case(Rule::TooManyNewlinesAtEndOfFile, Path::new("W391_1.py"))]
|
||||
#[test_case(Rule::TooManyNewlinesAtEndOfFile, Path::new("W391_2.py"))]
|
||||
#[test_case(Rule::TooManyNewlinesAtEndOfFile, Path::new("W391_3.py"))]
|
||||
#[test_case(Rule::TooManyNewlinesAtEndOfFile, Path::new("W391_4.py"))]
|
||||
// E741 has different behaviour for `.pyi` files in preview mode
|
||||
#[test_case(Rule::AmbiguousVariableName, Path::new("E741.pyi"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
|
|
@ -102,3 +102,24 @@ pub fn is_matplotlib_activation(stmt: &Stmt, semantic: &SemanticModel) -> bool {
|
|||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["matplotlib", "use"]))
|
||||
}
|
||||
|
||||
/// Returns `true` if a [`Stmt`] is a `pytest.importorskip()` call, as in:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
/// pytest.importorskip("foo.bar")
|
||||
/// ```
|
||||
pub fn is_pytest_importorskip(stmt: &Stmt, semantic: &SemanticModel) -> bool {
|
||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||
return false;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["pytest", "importorskip"])
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue