From 3d8a799ce797f8e120f1e21b7059875c886310ce Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 11 Dec 2023 10:31:45 -0500 Subject: [PATCH] Allow matplotlib.use calls to intersperse imports --- .../test/fixtures/pycodestyle/E402.py | 18 +++++--- crates/ruff_linter/src/checkers/ast/mod.rs | 1 + ...les__pycodestyle__tests__E402_E402.py.snap | 44 ++++++++++++++----- ...destyle__tests__preview__E402_E402.py.snap | 24 +++++----- .../src/analyze/imports.rs | 18 ++++++++ 5 files changed, 75 insertions(+), 30 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py index 8910ff6007e5f..fdd0e32ee29f8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py @@ -24,21 +24,27 @@ import f -__some__magic = 1 +import matplotlib + +matplotlib.use("Agg") import g +__some__magic = 1 + +import h + def foo() -> None: - import h + import i if __name__ == "__main__": - import i + import j -import j; import k +import k; import l if __name__ == "__main__": - import l; \ -import m + import m; \ +import n diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 6c347af7d1844..5d525550f5375 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -306,6 +306,7 @@ where if !(self.semantic.seen_import_boundary() || helpers::is_assignment_to_a_dunder(stmt) || helpers::in_nested_block(self.semantic.current_statements()) + || imports::is_matplotlib_activation(stmt, self.semantic()) || self.settings.preview.is_enabled() && imports::is_sys_path_modification(stmt, self.semantic())) { diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap index 44d7f5870ac56..072290ae87cf6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap @@ -8,30 +8,50 @@ E402.py:25:1: E402 Module level import not at top of file 25 | import f | ^^^^^^^^ E402 26 | -27 | __some__magic = 1 +27 | import matplotlib | -E402.py:29:1: E402 Module level import not at top of file +E402.py:27:1: E402 Module level import not at top of file | -27 | __some__magic = 1 +25 | import f +26 | +27 | import matplotlib + | ^^^^^^^^^^^^^^^^^ E402 28 | -29 | import g +29 | matplotlib.use("Agg") + | + +E402.py:31:1: E402 Module level import not at top of file + | +29 | matplotlib.use("Agg") +30 | +31 | import g + | ^^^^^^^^ E402 +32 | +33 | __some__magic = 1 + | + +E402.py:35:1: E402 Module level import not at top of file + | +33 | __some__magic = 1 +34 | +35 | import h | ^^^^^^^^ E402 | -E402.py:39:1: E402 Module level import not at top of file +E402.py:45:1: E402 Module level import not at top of file | -37 | import i -38 | -39 | import j; import k +43 | import j +44 | +45 | import k; import l | ^^^^^^^^ E402 | -E402.py:39:11: E402 Module level import not at top of file +E402.py:45:11: E402 Module level import not at top of file | -37 | import i -38 | -39 | import j; import k +43 | import j +44 | +45 | import k; import l | ^^^^^^^^ E402 | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap index cf3a61872dd85..7ec9e200b32e7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap @@ -1,27 +1,27 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E402.py:29:1: E402 Module level import not at top of file +E402.py:35:1: E402 Module level import not at top of file | -27 | __some__magic = 1 -28 | -29 | import g +33 | __some__magic = 1 +34 | +35 | import h | ^^^^^^^^ E402 | -E402.py:39:1: E402 Module level import not at top of file +E402.py:45:1: E402 Module level import not at top of file | -37 | import i -38 | -39 | import j; import k +43 | import j +44 | +45 | import k; import l | ^^^^^^^^ E402 | -E402.py:39:11: E402 Module level import not at top of file +E402.py:45:11: E402 Module level import not at top of file | -37 | import i -38 | -39 | import j; import k +43 | import j +44 | +45 | import k; import l | ^^^^^^^^ E402 | diff --git a/crates/ruff_python_semantic/src/analyze/imports.rs b/crates/ruff_python_semantic/src/analyze/imports.rs index cc9d219775b2f..a4a9ddc134486 100644 --- a/crates/ruff_python_semantic/src/analyze/imports.rs +++ b/crates/ruff_python_semantic/src/analyze/imports.rs @@ -35,3 +35,21 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool { ) }) } + +/// Returns `true` if a [`Stmt`] is a `matplotlib.use` activation, as in: +/// ```python +/// import matplotlib +/// +/// matplotlib.use("Agg") +/// ``` +pub fn is_matplotlib_activation(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_call_path(func.as_ref()) + .is_some_and(|call_path| matches!(call_path.as_slice(), ["matplotlib", "use"])) +}