-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6050bab
commit 181ccaa
Showing
8 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
52 changes: 52 additions & 0 deletions
52
crates/ruff_linter/resources/test/fixtures/pylint/global_variable_undefined.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
crates/ruff_linter/src/rules/pylint/rules/global_variable_undefined.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
...nt/snapshots/ruff_linter__rules__pylint__tests__PLW0601_global_variable_undefined.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
| |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.