From 6181b394fc10fe8d040aa7991bdae608e136b3d0 Mon Sep 17 00:00:00 2001 From: Tibor Reiss Date: Sun, 7 Apr 2024 13:18:19 +0200 Subject: [PATCH 1/5] Add W0601 --- .../pylint/global_variable_undefined.py | 52 +++++++ .../src/checkers/ast/analyze/statement.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pylint/mod.rs | 4 + .../pylint/rules/global_variable_undefined.rs | 128 ++++++++++++++++++ .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + ..._PLW0601_global_variable_undefined.py.snap | 11 ++ ruff.schema.json | 1 + 8 files changed, 202 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py new file mode 100644 index 0000000000000..cb230b4067f89 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py @@ -0,0 +1,52 @@ +# pylint: disable=invalid-name, import-outside-toplevel, too-few-public-methods, unused-import +# pylint: disable=missing-module-docstring, missing-function-docstring, missing-class-docstring +# pylint: disable=global-at-module-level, global-statement, global-variable-not-assigned +CONSTANT = 1 + + +def FUNC(): + pass + + +class CLASS: + pass + + +# BAD +def global_variable_undefined(): + global SOMEVAR # [global-variable-undefined] + SOMEVAR = 2 + + +# OK +def global_constant(): + global CONSTANT + print(CONSTANT) + + +def global_with_import(): + global sys + import sys + + +def global_with_import_from(): + global namedtuple + from collections import namedtuple + + +def override_func(): + global FUNC + + def FUNC(): + pass + + FUNC() + + +def override_class(): + global CLASS + + class CLASS(): + pass + + CLASS() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 6e6e572993f23..bbb5be5e91063 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -28,6 +28,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pycodestyle::rules::ambiguous_variable_name(checker, name, name.range()); } } + if checker.enabled(Rule::GlobalVariableUndefined) { + pylint::rules::global_variable_undefined(checker, stmt); + } } Stmt::Nonlocal(nonlocal @ ast::StmtNonlocal { names, range: _ }) => { if checker.enabled(Rule::AmbiguousVariableName) { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index e463c338269ca..e5d374aee5049 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -276,6 +276,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W0211") => (RuleGroup::Stable, rules::pylint::rules::BadStaticmethodArgument), (Pylint, "W0245") => (RuleGroup::Stable, rules::pylint::rules::SuperWithoutBrackets), (Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf), + (Pylint, "W0601") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableUndefined), (Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned), (Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement), (Pylint, "W0604") => (RuleGroup::Stable, rules::pylint::rules::GlobalAtModuleLevel), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 8a61555b950c3..40e4a5c46f356 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -64,6 +64,10 @@ mod tests { Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py") )] + #[test_case( + Rule::GlobalVariableUndefined, + Path::new("global_variable_undefined.py") + )] #[test_case(Rule::ImportOutsideTopLevel, Path::new("import_outside_top_level.py"))] #[test_case( Rule::ImportPrivateName, diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs new file mode 100644 index 0000000000000..7328530b7ddb7 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs @@ -0,0 +1,128 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr, Stmt, StmtGlobal}; +use ruff_python_semantic::{BindingKind, ScopeKind}; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks that all `global` variables are indeed defined on module level +/// +/// ## Why is this bad? +/// If the module level declaration is missing, then either if was +/// forgotten or the `global` can be omitted. +/// +/// ## Example +/// ```python +/// def foo(): +/// global var # [global-variable-undefined] +/// var = 10 +/// print(var) +/// ``` +/// +/// Use instead: +/// ```python +/// var = 1 +/// +/// +/// def foo(): +/// global var +/// var = 10 +/// print(var) +/// ``` +#[violation] +pub struct GlobalVariableUndefined { + name: String, +} + +impl Violation for GlobalVariableUndefined { + #[derive_message_formats] + fn message(&self) -> String { + let GlobalVariableUndefined { name } = self; + format!("Global variable `{name}` is undefined at the module") + } +} + +/// PLW0601 +pub(crate) fn global_variable_undefined(checker: &mut Checker, stmt: &Stmt) { + if checker.semantic().current_scope().kind.is_module() { + return; + } + + let Stmt::Global(StmtGlobal { names, range }) = stmt else { + return; + }; + let Some(module_scope) = checker + .semantic() + .current_scopes() + .find(|scope| scope.kind.is_module()) + else { + return; + }; + let imported_names = get_imports(checker); + let mut undefined_names = vec![]; + + for name in names { + // Skip if imported names + if imported_names.contains(&name.as_str()) { + continue; + } + // Skip if module level class or function definition + let Some(binding_id) = module_scope.get(name) else { + continue; + }; + let binding = checker.semantic().binding(binding_id); + if matches!( + binding.kind, + BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) + ) { + continue; + } + // Skip if module level definition + let Some(node_id) = binding.source else { + continue; + }; + let node = checker.semantic().node(node_id); + if let Some(Expr::Name(ast::ExprName { .. })) = node.as_expression() { + continue; + }; + + undefined_names.push(name); + } + + for name in undefined_names { + checker.diagnostics.push(Diagnostic::new( + GlobalVariableUndefined { + name: name.to_string(), + }, + *range, + )); + } +} + +fn get_imports<'a>(checker: &'a Checker) -> Vec<&'a str> { + // Get all names imported in the current scope + let Some(fs) = checker + .semantic() + .current_scopes() + .find(|scope| scope.kind.is_function()) + else { + return vec![]; + }; + let ScopeKind::Function(ast::StmtFunctionDef { body, .. }) = fs.kind else { + return vec![]; + }; + let mut import_names = vec![]; + for stmt in body { + match stmt { + Stmt::Import(ast::StmtImport { names, .. }) + | Stmt::ImportFrom(ast::StmtImportFrom { names, .. }) => { + for name in names { + import_names.push(name.name.as_str()); + } + } + _ => (), + } + } + import_names +} diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index bb14d868f71a0..e25dfc92fe2ad 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -22,6 +22,7 @@ pub(crate) use eq_without_hash::*; pub(crate) use global_at_module_level::*; pub(crate) use global_statement::*; pub(crate) use global_variable_not_assigned::*; +pub(crate) use global_variable_undefined::*; pub(crate) use if_stmt_min_max::*; pub(crate) use import_outside_top_level::*; pub(crate) use import_private_name::*; @@ -125,6 +126,7 @@ mod eq_without_hash; mod global_at_module_level; mod global_statement; mod global_variable_not_assigned; +mod global_variable_undefined; mod if_stmt_min_max; mod import_outside_top_level; mod import_private_name; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap new file mode 100644 index 0000000000000..631cc0f1fe3fc --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap @@ -0,0 +1,11 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +global_variable_undefined.py:17:5: PLW0601 Global variable `SOMEVAR` is undefined at the module + | +15 | # BAD +16 | def global_variable_undefined(): +17 | global SOMEVAR # [global-variable-undefined] + | ^^^^^^^^^^^^^^ PLW0601 +18 | SOMEVAR = 2 + | diff --git a/ruff.schema.json b/ruff.schema.json index c4adb82957e41..dc68cbc5ffef9 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3589,6 +3589,7 @@ "PLW0406", "PLW06", "PLW060", + "PLW0601", "PLW0602", "PLW0603", "PLW0604", From 5a61a5b4fed45495e008cae093b2e5670e476d13 Mon Sep 17 00:00:00 2001 From: Tibor Reiss Date: Thu, 16 May 2024 08:55:59 +0200 Subject: [PATCH 2/5] Do not check star imports --- .../pylint/global_variable_undefined_star_import.py | 10 ++++++++++ crates/ruff_linter/src/rules/pylint/mod.rs | 4 ++++ .../rules/pylint/rules/global_variable_undefined.rs | 11 ++++++++++- ...0601_global_variable_undefined_star_import.py.snap | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined_star_import.py create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined_star_import.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined_star_import.py b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined_star_import.py new file mode 100644 index 0000000000000..5f49650dca2bd --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined_star_import.py @@ -0,0 +1,10 @@ +# pylint: disable=missing-module-docstring, missing-function-docstring +# pylint: disable=redefined-builtin, unnecessary-lambda-assignment +# pylint: disable=global-statement, unused-wildcard-import, wildcard-import +from os import * + + +# OK +def global_star_import(): + global system + system = lambda _: None diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 40e4a5c46f356..c31e808e62115 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -68,6 +68,10 @@ mod tests { Rule::GlobalVariableUndefined, Path::new("global_variable_undefined.py") )] + #[test_case( + Rule::GlobalVariableUndefined, + Path::new("global_variable_undefined_star_import.py") + )] #[test_case(Rule::ImportOutsideTopLevel, Path::new("import_outside_top_level.py"))] #[test_case( Rule::ImportPrivateName, diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs index 7328530b7ddb7..371d690cde9b8 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt, StmtGlobal}; -use ruff_python_semantic::{BindingKind, ScopeKind}; +use ruff_python_semantic::{BindingKind, Scope, ScopeKind}; use crate::checkers::ast::Checker; @@ -52,6 +52,15 @@ pub(crate) fn global_variable_undefined(checker: &mut Checker, stmt: &Stmt) { let Stmt::Global(StmtGlobal { names, range }) = stmt else { return; }; + + if checker + .semantic() + .current_scopes() + .any(Scope::uses_star_imports) + { + return; + } + let Some(module_scope) = checker .semantic() .current_scopes() diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined_star_import.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined_star_import.py.snap new file mode 100644 index 0000000000000..889e8380a5f63 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined_star_import.py.snap @@ -0,0 +1,3 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- From 91d15a7b0820ef9027f2514ae82a7f8a1929228d Mon Sep 17 00:00:00 2001 From: Tibor Reiss <75096465+tibor-reiss@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:45:17 +0200 Subject: [PATCH 3/5] Use suggestion from CR Co-authored-by: Micha Reiser --- crates/ruff_linter/src/codes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index e5d374aee5049..eff99fb8af1a7 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -276,7 +276,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W0211") => (RuleGroup::Stable, rules::pylint::rules::BadStaticmethodArgument), (Pylint, "W0245") => (RuleGroup::Stable, rules::pylint::rules::SuperWithoutBrackets), (Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf), - (Pylint, "W0601") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableUndefined), + (Pylint, "W0601") => (RuleGroup::Preview, rules::pylint::rules::GlobalVariableUndefined), (Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned), (Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement), (Pylint, "W0604") => (RuleGroup::Stable, rules::pylint::rules::GlobalAtModuleLevel), From 3d4dfd26aa25eadccd22c48e7ca8f95e70ced64e Mon Sep 17 00:00:00 2001 From: Tibor Reiss Date: Sun, 29 Sep 2024 16:44:41 +0200 Subject: [PATCH 4/5] Fix for undefined global --- .../pylint/global_variable_undefined.py | 8 +++ .../pylint/rules/global_variable_undefined.rs | 52 +++++++++++-------- ..._PLW0601_global_variable_undefined.py.snap | 10 ++-- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py index cb230b4067f89..46487c651eba8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py @@ -2,6 +2,7 @@ # pylint: disable=missing-module-docstring, missing-function-docstring, missing-class-docstring # pylint: disable=global-at-module-level, global-statement, global-variable-not-assigned CONSTANT = 1 +UNDEFINED: int def FUNC(): @@ -22,6 +23,10 @@ def global_variable_undefined(): def global_constant(): global CONSTANT print(CONSTANT) + global UNDEFINED + UNDEFINED = 1 + global CONSTANT_2 + print(CONSTANT_2) def global_with_import(): @@ -50,3 +55,6 @@ class CLASS(): pass CLASS() + + +CONSTANT_2 = 2 diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs index 371d690cde9b8..1c6e4bb7b125d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt, StmtGlobal}; -use ruff_python_semantic::{BindingKind, Scope, ScopeKind}; +use ruff_python_semantic::{BindingId, BindingKind, Scope, ScopeKind}; use crate::checkers::ast::Checker; @@ -61,13 +61,7 @@ pub(crate) fn global_variable_undefined(checker: &mut Checker, stmt: &Stmt) { return; } - let Some(module_scope) = checker - .semantic() - .current_scopes() - .find(|scope| scope.kind.is_module()) - else { - return; - }; + let module_scope = checker.semantic().global_scope(); let imported_names = get_imports(checker); let mut undefined_names = vec![]; @@ -76,25 +70,19 @@ pub(crate) fn global_variable_undefined(checker: &mut Checker, stmt: &Stmt) { if imported_names.contains(&name.as_str()) { continue; } - // Skip if module level class or function definition + // Test binding which has been already defined let Some(binding_id) = module_scope.get(name) else { continue; }; - let binding = checker.semantic().binding(binding_id); - if matches!( - binding.kind, - BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) - ) { + if is_global_binding(checker, binding_id) { continue; } - // Skip if module level definition - let Some(node_id) = binding.source else { - continue; - }; - let node = checker.semantic().node(node_id); - if let Some(Expr::Name(ast::ExprName { .. })) = node.as_expression() { - continue; - }; + // Test binding which has been only declared + if let Some(shadowed_binding_id) = module_scope.shadowed_binding(binding_id) { + if is_global_binding(checker, shadowed_binding_id) { + continue; + } + } undefined_names.push(name); } @@ -135,3 +123,23 @@ fn get_imports<'a>(checker: &'a Checker) -> Vec<&'a str> { } import_names } + +fn is_global_binding(checker: &Checker, binding_id: BindingId) -> bool { + let binding = checker.semantic().binding(binding_id); + // Skip if module level class or function definition + if matches!( + binding.kind, + BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) + ) { + return true; + } + // Skip if module level definition + let Some(node_id) = binding.source else { + return true; + }; + let node = checker.semantic().node(node_id); + if let Some(Expr::Name(ast::ExprName { .. })) = node.as_expression() { + return true; + }; + false +} diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap index 631cc0f1fe3fc..a53d4d148d5e3 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap @@ -1,11 +1,11 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -global_variable_undefined.py:17:5: PLW0601 Global variable `SOMEVAR` is undefined at the module +global_variable_undefined.py:18:5: PLW0601 Global variable `SOMEVAR` is undefined at the module | -15 | # BAD -16 | def global_variable_undefined(): -17 | global SOMEVAR # [global-variable-undefined] +16 | # BAD +17 | def global_variable_undefined(): +18 | global SOMEVAR # [global-variable-undefined] | ^^^^^^^^^^^^^^ PLW0601 -18 | SOMEVAR = 2 +19 | SOMEVAR = 2 | From adb72371cabf889147f190b23dc5acf96bef228f Mon Sep 17 00:00:00 2001 From: Tibor Reiss Date: Sun, 29 Sep 2024 21:15:16 +0200 Subject: [PATCH 5/5] Take care of all imports --- .../test/fixtures/pylint/global_variable_undefined.py | 8 ++++++++ .../rules/pylint/rules/global_variable_undefined.rs | 7 +++++++ ...t__tests__PLW0601_global_variable_undefined.py.snap | 10 +++++----- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py index 46487c651eba8..5f7a59930d107 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py @@ -1,6 +1,10 @@ # pylint: disable=invalid-name, import-outside-toplevel, too-few-public-methods, unused-import # pylint: disable=missing-module-docstring, missing-function-docstring, missing-class-docstring # pylint: disable=global-at-module-level, global-statement, global-variable-not-assigned +# pylint: diable=redefined-outer-name +import dataclasses +from os import getenv + CONSTANT = 1 UNDEFINED: int @@ -27,6 +31,10 @@ def global_constant(): UNDEFINED = 1 global CONSTANT_2 print(CONSTANT_2) + global dataclasses + dataclasses = 3 + global getenv + getenv = 4 def global_with_import(): diff --git a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs index 1c6e4bb7b125d..d985f3146a4e4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs @@ -126,6 +126,13 @@ fn get_imports<'a>(checker: &'a Checker) -> Vec<&'a str> { fn is_global_binding(checker: &Checker, binding_id: BindingId) -> bool { let binding = checker.semantic().binding(binding_id); + // Skip if import + if matches!( + binding.kind, + BindingKind::FromImport(_) | BindingKind::Import(_) | BindingKind::FutureImport + ) { + return true; + } // Skip if module level class or function definition if matches!( binding.kind, diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap index a53d4d148d5e3..1a568b517198a 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap @@ -1,11 +1,11 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -global_variable_undefined.py:18:5: PLW0601 Global variable `SOMEVAR` is undefined at the module +global_variable_undefined.py:22:5: PLW0601 Global variable `SOMEVAR` is undefined at the module | -16 | # BAD -17 | def global_variable_undefined(): -18 | global SOMEVAR # [global-variable-undefined] +20 | # BAD +21 | def global_variable_undefined(): +22 | global SOMEVAR # [global-variable-undefined] | ^^^^^^^^^^^^^^ PLW0601 -19 | SOMEVAR = 2 +23 | SOMEVAR = 2 |