From 24bab7e82e59bb4940cf383c4f393de6e024e80d Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:51:51 +0100 Subject: [PATCH] [pycodestyle] Exempt `sys.path += ...` calls (E402) (#15980) ## Summary The PR addresses issue #15886 . --- .../test/fixtures/pycodestyle/E402_4.py | 7 +++ .../ruff_linter/src/rules/pycodestyle/mod.rs | 1 + ...s__pycodestyle__tests__E402_E402_4.py.snap | 3 ++ .../src/analyze/imports.rs | 54 ++++++++++--------- 4 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_4.py create mode 100644 crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_4.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_4.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_4.py new file mode 100644 index 0000000000..7bcc01f89b --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_4.py @@ -0,0 +1,7 @@ +import os +import sys + +sys.path += [os.path.dirname(__file__)] +sys.path += ["../"] + +from package import module \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pycodestyle/mod.rs b/crates/ruff_linter/src/rules/pycodestyle/mod.rs index 9c55fedb07..84b3363f10 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/mod.rs @@ -44,6 +44,7 @@ mod tests { #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_1.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_2.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_3.py"))] + #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_4.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.ipynb"))] #[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))] #[test_case(Rule::MultipleStatementsOnOneLineColon, Path::new("E70.py"))] diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_4.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_4.py.snap new file mode 100644 index 0000000000..195339b453 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_4.py.snap @@ -0,0 +1,3 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- diff --git a/crates/ruff_python_semantic/src/analyze/imports.rs b/crates/ruff_python_semantic/src/analyze/imports.rs index a88103693b..19802a8092 100644 --- a/crates/ruff_python_semantic/src/analyze/imports.rs +++ b/crates/ruff_python_semantic/src/analyze/imports.rs @@ -8,33 +8,37 @@ use crate::SemanticModel; /// import sys /// /// sys.path.append("../") +/// sys.path += ["../"] /// ``` pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool { - let Stmt::Expr(ast::StmtExpr { value, range: _ }) = 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(), - [ - "sys", - "path", - "append" - | "insert" - | "extend" - | "remove" - | "pop" - | "clear" - | "reverse" - | "sort" - ] - ) - }) + match stmt { + Stmt::Expr(ast::StmtExpr { value, range: _ }) => match value.as_ref() { + Expr::Call(ast::ExprCall { func, .. }) => semantic + .resolve_qualified_name(func.as_ref()) + .is_some_and(|qualified_name| { + matches!( + qualified_name.segments(), + [ + "sys", + "path", + "append" + | "insert" + | "extend" + | "remove" + | "pop" + | "clear" + | "reverse" + | "sort" + ] + ) + }), + _ => false, + }, + Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => semantic + .resolve_qualified_name(map_subscript(target)) + .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "path"])), + _ => false, + } } /// Returns `true` if a [`Stmt`] is an `os.environ` modification, as in: