mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[pylint
] W0603: global-statement
(#3227)
Implements pylint rule [W0603: global-statement](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/global-statement.html).
Currently checks for global statement usage in a few StmtKinds (as tested in the `pylint` `global-statement` test case [here](b70d2abd7f/tests/functional/g/globals.py
)):
* Assign
* AugAssign
* ClassDef
* FunctionDef | AsyncFunctionDef
* Import
* ImportFrom
* Delete
This commit is contained in:
parent
36d134fd41
commit
4b5538f74e
9 changed files with 299 additions and 1 deletions
75
crates/ruff/resources/test/fixtures/pylint/global_statement.py
vendored
Normal file
75
crates/ruff/resources/test/fixtures/pylint/global_statement.py
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# Adapted from:
|
||||||
|
# https://github.com/PyCQA/pylint/blob/b70d2abd7fabe9bfd735a30b593b9cd5eaa36194/tests/functional/g/globals.py
|
||||||
|
|
||||||
|
CONSTANT = 1
|
||||||
|
|
||||||
|
|
||||||
|
def FUNC():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CLASS:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def fix_constant(value):
|
||||||
|
"""All this is ok, but try not to use `global` ;)"""
|
||||||
|
global CONSTANT # [global-statement]
|
||||||
|
print(CONSTANT)
|
||||||
|
CONSTANT = value
|
||||||
|
|
||||||
|
|
||||||
|
def global_with_import():
|
||||||
|
"""Should only warn for global-statement when using `Import` node"""
|
||||||
|
global sys # [global-statement]
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def global_with_import_from():
|
||||||
|
"""Should only warn for global-statement when using `ImportFrom` node"""
|
||||||
|
global namedtuple # [global-statement]
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
def global_del():
|
||||||
|
"""Deleting the global name prevents `global-variable-not-assigned`"""
|
||||||
|
global CONSTANT # [global-statement]
|
||||||
|
print(CONSTANT)
|
||||||
|
del CONSTANT
|
||||||
|
|
||||||
|
|
||||||
|
def global_operator_assign():
|
||||||
|
"""Operator assigns should only throw a global statement error"""
|
||||||
|
global CONSTANT # [global-statement]
|
||||||
|
print(CONSTANT)
|
||||||
|
CONSTANT += 1
|
||||||
|
|
||||||
|
|
||||||
|
def global_function_assign():
|
||||||
|
"""Function assigns should only throw a global statement error"""
|
||||||
|
global CONSTANT # [global-statement]
|
||||||
|
|
||||||
|
def CONSTANT():
|
||||||
|
pass
|
||||||
|
|
||||||
|
CONSTANT()
|
||||||
|
|
||||||
|
|
||||||
|
def override_func():
|
||||||
|
"""Overriding a function should only throw a global statement error"""
|
||||||
|
global FUNC # [global-statement]
|
||||||
|
|
||||||
|
def FUNC():
|
||||||
|
pass
|
||||||
|
|
||||||
|
FUNC()
|
||||||
|
|
||||||
|
|
||||||
|
def override_class():
|
||||||
|
"""Overriding a class should only throw a global statement error"""
|
||||||
|
global CLASS # [global-statement]
|
||||||
|
|
||||||
|
class CLASS:
|
||||||
|
pass
|
||||||
|
|
||||||
|
CLASS()
|
|
@ -556,6 +556,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
pylint::rules::global_statement(self, name);
|
||||||
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
|
@ -834,6 +838,9 @@ where
|
||||||
self.diagnostics.push(diagnostic);
|
self.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
pylint::rules::global_statement(self, name);
|
||||||
|
}
|
||||||
if self.settings.rules.enabled(&Rule::UselessObjectInheritance) {
|
if self.settings.rules.enabled(&Rule::UselessObjectInheritance) {
|
||||||
pyupgrade::rules::useless_object_inheritance(self, stmt, name, bases, keywords);
|
pyupgrade::rules::useless_object_inheritance(self, stmt, name, bases, keywords);
|
||||||
}
|
}
|
||||||
|
@ -937,6 +944,17 @@ where
|
||||||
{
|
{
|
||||||
pycodestyle::rules::module_import_not_at_top_of_file(self, stmt);
|
pycodestyle::rules::module_import_not_at_top_of_file(self, stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
for name in names.iter() {
|
||||||
|
if let Some(asname) = name.node.asname.as_ref() {
|
||||||
|
pylint::rules::global_statement(self, asname);
|
||||||
|
} else {
|
||||||
|
pylint::rules::global_statement(self, &name.node.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
|
if self.settings.rules.enabled(&Rule::RewriteCElementTree) {
|
||||||
pyupgrade::rules::replace_c_element_tree(self, stmt);
|
pyupgrade::rules::replace_c_element_tree(self, stmt);
|
||||||
}
|
}
|
||||||
|
@ -1194,6 +1212,16 @@ where
|
||||||
pycodestyle::rules::module_import_not_at_top_of_file(self, stmt);
|
pycodestyle::rules::module_import_not_at_top_of_file(self, stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
for name in names.iter() {
|
||||||
|
if let Some(asname) = name.node.asname.as_ref() {
|
||||||
|
pylint::rules::global_statement(self, asname);
|
||||||
|
} else {
|
||||||
|
pylint::rules::global_statement(self, &name.node.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::UnnecessaryFutureImport)
|
if self.settings.rules.enabled(&Rule::UnnecessaryFutureImport)
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
{
|
{
|
||||||
|
@ -1576,6 +1604,12 @@ where
|
||||||
}
|
}
|
||||||
StmtKind::AugAssign { target, .. } => {
|
StmtKind::AugAssign { target, .. } => {
|
||||||
self.handle_node_load(target);
|
self.handle_node_load(target);
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
if let ExprKind::Name { id, .. } = &target.node {
|
||||||
|
pylint::rules::global_statement(self, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StmtKind::If { test, body, orelse } => {
|
StmtKind::If { test, body, orelse } => {
|
||||||
if self.settings.rules.enabled(&Rule::IfTuple) {
|
if self.settings.rules.enabled(&Rule::IfTuple) {
|
||||||
|
@ -1846,6 +1880,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
for target in targets.iter() {
|
||||||
|
if let ExprKind::Name { id, .. } = &target.node {
|
||||||
|
pylint::rules::global_statement(self, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::UselessMetaclassType) {
|
if self.settings.rules.enabled(&Rule::UselessMetaclassType) {
|
||||||
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
pyupgrade::rules::useless_metaclass_type(self, stmt, value, targets);
|
||||||
}
|
}
|
||||||
|
@ -1896,7 +1938,15 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StmtKind::Delete { .. } => {}
|
StmtKind::Delete { targets } => {
|
||||||
|
if self.settings.rules.enabled(&Rule::GlobalStatement) {
|
||||||
|
for target in targets.iter() {
|
||||||
|
if let ExprKind::Name { id, .. } = &target.node {
|
||||||
|
pylint::rules::global_statement(self, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
StmtKind::Expr { value, .. } => {
|
StmtKind::Expr { value, .. } => {
|
||||||
if self.settings.rules.enabled(&Rule::UselessComparison) {
|
if self.settings.rules.enabled(&Rule::UselessComparison) {
|
||||||
flake8_bugbear::rules::useless_comparison(self, value);
|
flake8_bugbear::rules::useless_comparison(self, value);
|
||||||
|
|
|
@ -149,6 +149,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Pylint, "R2004") => Rule::MagicValueComparison,
|
(Pylint, "R2004") => Rule::MagicValueComparison,
|
||||||
(Pylint, "W0120") => Rule::UselessElseOnLoop,
|
(Pylint, "W0120") => Rule::UselessElseOnLoop,
|
||||||
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
|
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
|
||||||
|
(Pylint, "W0603") => Rule::GlobalStatement,
|
||||||
(Pylint, "R0911") => Rule::TooManyReturnStatements,
|
(Pylint, "R0911") => Rule::TooManyReturnStatements,
|
||||||
(Pylint, "R0913") => Rule::TooManyArguments,
|
(Pylint, "R0913") => Rule::TooManyArguments,
|
||||||
(Pylint, "R0912") => Rule::TooManyBranches,
|
(Pylint, "R0912") => Rule::TooManyBranches,
|
||||||
|
|
|
@ -149,6 +149,7 @@ ruff_macros::register_rules!(
|
||||||
rules::pylint::rules::ConsiderUsingSysExit,
|
rules::pylint::rules::ConsiderUsingSysExit,
|
||||||
rules::pylint::rules::MagicValueComparison,
|
rules::pylint::rules::MagicValueComparison,
|
||||||
rules::pylint::rules::UselessElseOnLoop,
|
rules::pylint::rules::UselessElseOnLoop,
|
||||||
|
rules::pylint::rules::GlobalStatement,
|
||||||
rules::pylint::rules::GlobalVariableNotAssigned,
|
rules::pylint::rules::GlobalVariableNotAssigned,
|
||||||
rules::pylint::rules::TooManyReturnStatements,
|
rules::pylint::rules::TooManyReturnStatements,
|
||||||
rules::pylint::rules::TooManyArguments,
|
rules::pylint::rules::TooManyArguments,
|
||||||
|
|
|
@ -39,6 +39,7 @@ mod tests {
|
||||||
#[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"); "PLR2004")]
|
#[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"); "PLR2004")]
|
||||||
#[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")]
|
#[test_case(Rule::UselessElseOnLoop, Path::new("useless_else_on_loop.py"); "PLW0120")]
|
||||||
#[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")]
|
#[test_case(Rule::GlobalVariableNotAssigned, Path::new("global_variable_not_assigned.py"); "PLW0602")]
|
||||||
|
#[test_case(Rule::GlobalStatement, Path::new("global_statement.py"); "PLW0603")]
|
||||||
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
|
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
|
||||||
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
|
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
|
||||||
#[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")]
|
#[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")]
|
||||||
|
|
74
crates/ruff/src/rules/pylint/rules/global_statement.rs
Normal file
74
crates/ruff/src/rules/pylint/rules/global_statement.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
|
use crate::violation::Violation;
|
||||||
|
use crate::Range;
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for the use of `global` statements to update identifiers.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Pylint discourages the use of `global` variables as global mutable
|
||||||
|
/// state is a common source of bugs and confusing behavior.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// var = 1
|
||||||
|
///
|
||||||
|
/// def foo():
|
||||||
|
/// global var # [global-statement]
|
||||||
|
/// var = 10
|
||||||
|
/// print(var)
|
||||||
|
///
|
||||||
|
/// foo()
|
||||||
|
/// print(var)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// var = 1
|
||||||
|
///
|
||||||
|
/// def foo():
|
||||||
|
/// print(var)
|
||||||
|
/// return 10
|
||||||
|
///
|
||||||
|
/// var = foo()
|
||||||
|
/// print(var)
|
||||||
|
/// ```
|
||||||
|
pub struct GlobalStatement {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
impl Violation for GlobalStatement {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let GlobalStatement { name } = self;
|
||||||
|
format!("Using the global statement to update `{name}` is discouraged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PLW0603
|
||||||
|
pub fn global_statement(checker: &mut Checker, name: &str) {
|
||||||
|
let scope = checker.current_scope();
|
||||||
|
if let Some(index) = scope.bindings.get(name) {
|
||||||
|
let binding = &checker.bindings[*index];
|
||||||
|
if binding.kind.is_global() {
|
||||||
|
let diagnostic = Diagnostic::new(
|
||||||
|
GlobalStatement {
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
// Match Pylint's behavior by reporting on the `global` statement`, rather
|
||||||
|
// than the variable usage.
|
||||||
|
Range::from_located(
|
||||||
|
binding
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.expect("`global` bindings should always have a `source`"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ pub use bidirectional_unicode::{bidirectional_unicode, BidirectionalUnicode};
|
||||||
pub use collapsible_else_if::{collapsible_else_if, CollapsibleElseIf};
|
pub use collapsible_else_if::{collapsible_else_if, CollapsibleElseIf};
|
||||||
pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant};
|
pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant};
|
||||||
pub use consider_using_sys_exit::{consider_using_sys_exit, ConsiderUsingSysExit};
|
pub use consider_using_sys_exit::{consider_using_sys_exit, ConsiderUsingSysExit};
|
||||||
|
pub use global_statement::{global_statement, GlobalStatement};
|
||||||
pub use global_variable_not_assigned::GlobalVariableNotAssigned;
|
pub use global_variable_not_assigned::GlobalVariableNotAssigned;
|
||||||
pub use invalid_all_format::{invalid_all_format, InvalidAllFormat};
|
pub use invalid_all_format::{invalid_all_format, InvalidAllFormat};
|
||||||
pub use invalid_all_object::{invalid_all_object, InvalidAllObject};
|
pub use invalid_all_object::{invalid_all_object, InvalidAllObject};
|
||||||
|
@ -37,6 +38,7 @@ mod bidirectional_unicode;
|
||||||
mod collapsible_else_if;
|
mod collapsible_else_if;
|
||||||
mod comparison_of_constant;
|
mod comparison_of_constant;
|
||||||
mod consider_using_sys_exit;
|
mod consider_using_sys_exit;
|
||||||
|
mod global_statement;
|
||||||
mod global_variable_not_assigned;
|
mod global_variable_not_assigned;
|
||||||
mod invalid_all_format;
|
mod invalid_all_format;
|
||||||
mod invalid_all_object;
|
mod invalid_all_object;
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pylint/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: CONSTANT
|
||||||
|
location:
|
||||||
|
row: 17
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 17
|
||||||
|
column: 19
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: sys
|
||||||
|
location:
|
||||||
|
row: 24
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 24
|
||||||
|
column: 14
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: namedtuple
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 21
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: CONSTANT
|
||||||
|
location:
|
||||||
|
row: 36
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 36
|
||||||
|
column: 19
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: CONSTANT
|
||||||
|
location:
|
||||||
|
row: 43
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 43
|
||||||
|
column: 19
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: CONSTANT
|
||||||
|
location:
|
||||||
|
row: 50
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 50
|
||||||
|
column: 19
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: FUNC
|
||||||
|
location:
|
||||||
|
row: 60
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 60
|
||||||
|
column: 15
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
GlobalStatement:
|
||||||
|
name: CLASS
|
||||||
|
location:
|
||||||
|
row: 70
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 70
|
||||||
|
column: 16
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -1859,6 +1859,7 @@
|
||||||
"PLW06",
|
"PLW06",
|
||||||
"PLW060",
|
"PLW060",
|
||||||
"PLW0602",
|
"PLW0602",
|
||||||
|
"PLW0603",
|
||||||
"PLW2",
|
"PLW2",
|
||||||
"PLW29",
|
"PLW29",
|
||||||
"PLW290",
|
"PLW290",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue