mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-23 13:05:06 +00:00
[ruff
] Implemented used-dummy-variable
(RUF052
) (#14611)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
a69dfd4a74
commit
bf0fd04e4e
12 changed files with 778 additions and 8 deletions
106
crates/ruff_linter/resources/test/fixtures/ruff/RUF052.py
vendored
Normal file
106
crates/ruff_linter/resources/test/fixtures/ruff/RUF052.py
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Correct
|
||||
|
||||
for _ in range(5):
|
||||
pass
|
||||
|
||||
_valid_type = int
|
||||
|
||||
_valid_var_1: _valid_type
|
||||
|
||||
_valid_var_1 = 1
|
||||
|
||||
_valid_var_2 = 2
|
||||
|
||||
_valid_var_3 = _valid_var_1 + _valid_var_2
|
||||
|
||||
def _valid_fun():
|
||||
pass
|
||||
|
||||
_valid_fun()
|
||||
|
||||
def fun(arg):
|
||||
_valid_unused_var = arg
|
||||
pass
|
||||
|
||||
_x = "global"
|
||||
|
||||
def fun():
|
||||
global _x
|
||||
return _x
|
||||
|
||||
def fun():
|
||||
__dunder__ = "dunder variable"
|
||||
return __dunder__
|
||||
|
||||
def fun():
|
||||
global _x
|
||||
_x = "reassigned global"
|
||||
return _x
|
||||
|
||||
class _ValidClass:
|
||||
pass
|
||||
|
||||
_ValidClass()
|
||||
|
||||
class ClassOk:
|
||||
_valid_private_cls_attr = 1
|
||||
|
||||
print(_valid_private_cls_attr)
|
||||
|
||||
def __init__(self):
|
||||
self._valid_private_ins_attr = 2
|
||||
print(self._valid_private_ins_attr)
|
||||
|
||||
def _valid_method(self):
|
||||
return self._valid_private_ins_attr
|
||||
|
||||
def method(arg):
|
||||
_valid_unused_var = arg
|
||||
return
|
||||
|
||||
def fun(x):
|
||||
_ = 1
|
||||
__ = 2
|
||||
___ = 3
|
||||
if x == 1:
|
||||
return _
|
||||
if x == 2:
|
||||
return __
|
||||
if x == 3:
|
||||
return ___
|
||||
return x
|
||||
|
||||
# Incorrect
|
||||
|
||||
class Class_:
|
||||
def fun(self):
|
||||
_var = "method variable" # [RUF052]
|
||||
return _var
|
||||
|
||||
def fun(_var): # [RUF052]
|
||||
return _var
|
||||
|
||||
def fun():
|
||||
_list = "built-in" # [RUF052]
|
||||
return _list
|
||||
|
||||
x = "global"
|
||||
|
||||
def fun():
|
||||
global x
|
||||
_x = "shadows global" # [RUF052]
|
||||
return _x
|
||||
|
||||
def foo():
|
||||
x = "outer"
|
||||
def bar():
|
||||
nonlocal x
|
||||
_x = "shadows nonlocal" # [RUF052]
|
||||
return _x
|
||||
bar()
|
||||
return x
|
||||
|
||||
def fun():
|
||||
x = "local"
|
||||
_x = "shadows local" # [RUF052]
|
||||
return _x
|
|
@ -10,6 +10,7 @@ use crate::rules::{
|
|||
/// Run lint rules over the [`Binding`]s.
|
||||
pub(crate) fn bindings(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::AssignmentInAssert,
|
||||
Rule::InvalidAllFormat,
|
||||
Rule::InvalidAllObject,
|
||||
Rule::NonAsciiName,
|
||||
|
@ -18,7 +19,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
|||
Rule::UnsortedDunderSlots,
|
||||
Rule::UnusedVariable,
|
||||
Rule::UnquotedTypeAlias,
|
||||
Rule::AssignmentInAssert,
|
||||
Rule::UsedDummyVariable,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
|
@ -88,6 +89,11 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
|||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UsedDummyVariable) {
|
||||
if let Some(diagnostic) = ruff::rules::used_dummy_variable(checker, binding) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::AssignmentInAssert) {
|
||||
if let Some(diagnostic) = ruff::rules::assignment_in_assert(checker, binding) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
|
|
@ -984,6 +984,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
|
||||
(Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral),
|
||||
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
|
||||
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
|
||||
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
|
|
|
@ -10,6 +10,7 @@ mod tests {
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_case::test_case;
|
||||
|
||||
|
@ -70,6 +71,7 @@ mod tests {
|
|||
#[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))]
|
||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
|
||||
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
@ -449,4 +451,32 @@ mod tests {
|
|||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
|
||||
fn custom_regexp_preset(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
regex_pattern: &str,
|
||||
id: u8,
|
||||
) -> Result<()> {
|
||||
// Compile the regex from the pattern string
|
||||
let regex = Regex::new(regex_pattern).unwrap();
|
||||
|
||||
let snapshot = format!(
|
||||
"custom_dummy_var_regexp_preset__{}_{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy(),
|
||||
id,
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
dummy_variable_rgx: regex,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ pub(crate) use unraw_re_pattern::*;
|
|||
pub(crate) use unsafe_markup_use::*;
|
||||
pub(crate) use unused_async::*;
|
||||
pub(crate) use unused_noqa::*;
|
||||
pub(crate) use used_dummy_variable::*;
|
||||
pub(crate) use useless_if_else::*;
|
||||
pub(crate) use zip_instead_of_pairwise::*;
|
||||
|
||||
|
@ -85,6 +86,7 @@ mod unraw_re_pattern;
|
|||
mod unsafe_markup_use;
|
||||
mod unused_async;
|
||||
mod unused_noqa;
|
||||
mod used_dummy_variable;
|
||||
mod useless_if_else;
|
||||
mod zip_instead_of_pairwise;
|
||||
|
||||
|
|
221
crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs
Normal file
221
crates/ruff_linter/src/rules/ruff/rules/used_dummy_variable.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::helpers::is_dunder;
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeId};
|
||||
use ruff_python_stdlib::{
|
||||
builtins::is_python_builtin, identifiers::is_identifier, keyword::is_keyword,
|
||||
};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{checkers::ast::Checker, renamer::Renamer};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for accesses of local dummy variables, excluding `_` and dunder variables.
|
||||
///
|
||||
/// By default, "dummy variables" are any variables with names that start with leading
|
||||
/// underscores. However, this is customisable using the `dummy-variable-rgx` setting).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Marking a variable with a leading underscore conveys that it is intentionally unused within the function or method.
|
||||
/// When these variables are later referenced in the code, it causes confusion and potential misunderstandings about
|
||||
/// the code's intention. A variable marked as "unused" being subsequently used suggests oversight or unintentional use.
|
||||
/// This detracts from the clarity and maintainability of the codebase.
|
||||
///
|
||||
/// Sometimes leading underscores are used to avoid variables shadowing other variables, Python builtins, or Python
|
||||
/// keywords. However, [PEP 8] recommends to use trailing underscores for this rather than leading underscores.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def function():
|
||||
/// _variable = 3
|
||||
/// return _variable + 1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def function():
|
||||
/// variable = 3
|
||||
/// return variable + 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix availability
|
||||
/// An fix is only available for variables that start with leading underscores.
|
||||
///
|
||||
/// ## Options
|
||||
/// - [`lint.dummy-variable-rgx`]
|
||||
///
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct UsedDummyVariable {
|
||||
name: String,
|
||||
shadowed_kind: Option<ShadowedKind>,
|
||||
}
|
||||
|
||||
impl Violation for UsedDummyVariable {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Local dummy variable `{}` is accessed", self.name)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
if let Some(shadowed_kind) = self.shadowed_kind {
|
||||
return Some(match shadowed_kind {
|
||||
ShadowedKind::BuiltIn => {
|
||||
"Prefer using trailing underscores to avoid shadowing a built-in".to_string()
|
||||
}
|
||||
ShadowedKind::Keyword => {
|
||||
"Prefer using trailing underscores to avoid shadowing a keyword".to_string()
|
||||
}
|
||||
ShadowedKind::Some => {
|
||||
"Prefer using trailing underscores to avoid shadowing a variable".to_string()
|
||||
}
|
||||
ShadowedKind::None => "Remove leading underscores".to_string(),
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF052
|
||||
pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding) -> Option<Diagnostic> {
|
||||
let name = binding.name(checker.source());
|
||||
|
||||
// Ignore `_` and dunder variables
|
||||
if name == "_" || is_dunder(name) {
|
||||
return None;
|
||||
}
|
||||
// only used variables
|
||||
if binding.is_unused() {
|
||||
return None;
|
||||
}
|
||||
// Only variables defined via function arguments or assignments.
|
||||
if !matches!(
|
||||
binding.kind,
|
||||
BindingKind::Argument | BindingKind::Assignment
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
// This excludes `global` and `nonlocal` variables.
|
||||
if binding.is_global() || binding.is_nonlocal() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
// Only variables defined in function scopes
|
||||
let scope = &semantic.scopes[binding.scope];
|
||||
if !scope.kind.is_function() {
|
||||
return None;
|
||||
}
|
||||
if !checker.settings.dummy_variable_rgx.is_match(name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let shadowed_kind = try_shadowed_kind(name, checker, binding.scope);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UsedDummyVariable {
|
||||
name: name.to_string(),
|
||||
shadowed_kind,
|
||||
},
|
||||
binding.range(),
|
||||
);
|
||||
|
||||
// If fix available
|
||||
if let Some(shadowed_kind) = shadowed_kind {
|
||||
// Get the possible fix based on the scope
|
||||
if let Some(fix) = get_possible_fix(name, shadowed_kind, binding.scope, checker) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
Renamer::rename(name, &fix, scope, semantic, checker.stylist())
|
||||
.map(|(edit, rest)| Fix::safe_edits(edit, rest))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some(diagnostic)
|
||||
}
|
||||
|
||||
/// Enumeration of various ways in which a binding can shadow other variables
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum ShadowedKind {
|
||||
/// The variable shadows a global, nonlocal or local symbol
|
||||
Some,
|
||||
/// The variable shadows a builtin symbol
|
||||
BuiltIn,
|
||||
/// The variable shadows a keyword
|
||||
Keyword,
|
||||
/// The variable does not shadow any other symbols
|
||||
None,
|
||||
}
|
||||
|
||||
/// Suggests a potential alternative name to resolve a shadowing conflict.
|
||||
fn get_possible_fix(
|
||||
name: &str,
|
||||
kind: ShadowedKind,
|
||||
scope_id: ScopeId,
|
||||
checker: &Checker,
|
||||
) -> Option<String> {
|
||||
// Remove leading underscores for processing
|
||||
let trimmed_name = name.trim_start_matches('_');
|
||||
|
||||
// Construct the potential fix name based on ShadowedKind
|
||||
let fix_name = match kind {
|
||||
ShadowedKind::Some | ShadowedKind::BuiltIn | ShadowedKind::Keyword => {
|
||||
format!("{trimmed_name}_") // Append an underscore
|
||||
}
|
||||
ShadowedKind::None => trimmed_name.to_string(),
|
||||
};
|
||||
|
||||
// Check if the fix name is again dummy identifier
|
||||
if checker.settings.dummy_variable_rgx.is_match(&fix_name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ensure the fix name is not already taken in the scope or enclosing scopes
|
||||
if !checker
|
||||
.semantic()
|
||||
.is_available_in_scope(&fix_name, scope_id)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check if the fix name is a valid identifier
|
||||
is_identifier(&fix_name).then_some(fix_name)
|
||||
}
|
||||
|
||||
/// Determines the kind of shadowing or conflict for a given variable name.
|
||||
fn try_shadowed_kind(name: &str, checker: &Checker, scope_id: ScopeId) -> Option<ShadowedKind> {
|
||||
// If the name starts with an underscore, we don't consider it
|
||||
if !name.starts_with('_') {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Trim the leading underscores for further checks
|
||||
let trimmed_name = name.trim_start_matches('_');
|
||||
|
||||
// Check the kind in order of precedence
|
||||
if is_keyword(trimmed_name) {
|
||||
return Some(ShadowedKind::Keyword);
|
||||
}
|
||||
|
||||
if is_python_builtin(
|
||||
trimmed_name,
|
||||
checker.settings.target_version.minor(),
|
||||
checker.source_type.is_ipynb(),
|
||||
) {
|
||||
return Some(ShadowedKind::BuiltIn);
|
||||
}
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.is_available_in_scope(trimmed_name, scope_id)
|
||||
{
|
||||
return Some(ShadowedKind::Some);
|
||||
}
|
||||
|
||||
// Default to no shadowing
|
||||
Some(ShadowedKind::None)
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
RUF052.py:77:9: RUF052 [*] Local dummy variable `_var` is accessed
|
||||
|
|
||||
75 | class Class_:
|
||||
76 | def fun(self):
|
||||
77 | _var = "method variable" # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
78 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
ℹ Safe fix
|
||||
74 74 |
|
||||
75 75 | class Class_:
|
||||
76 76 | def fun(self):
|
||||
77 |- _var = "method variable" # [RUF052]
|
||||
78 |- return _var
|
||||
77 |+ var = "method variable" # [RUF052]
|
||||
78 |+ return var
|
||||
79 79 |
|
||||
80 80 | def fun(_var): # [RUF052]
|
||||
81 81 | return _var
|
||||
|
||||
RUF052.py:80:9: RUF052 [*] Local dummy variable `_var` is accessed
|
||||
|
|
||||
78 | return _var
|
||||
79 |
|
||||
80 | def fun(_var): # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
81 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
ℹ Safe fix
|
||||
77 77 | _var = "method variable" # [RUF052]
|
||||
78 78 | return _var
|
||||
79 79 |
|
||||
80 |-def fun(_var): # [RUF052]
|
||||
81 |- return _var
|
||||
80 |+def fun(var): # [RUF052]
|
||||
81 |+ return var
|
||||
82 82 |
|
||||
83 83 | def fun():
|
||||
84 84 | _list = "built-in" # [RUF052]
|
||||
|
||||
RUF052.py:84:5: RUF052 [*] Local dummy variable `_list` is accessed
|
||||
|
|
||||
83 | def fun():
|
||||
84 | _list = "built-in" # [RUF052]
|
||||
| ^^^^^ RUF052
|
||||
85 | return _list
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
|
||||
ℹ Safe fix
|
||||
81 81 | return _var
|
||||
82 82 |
|
||||
83 83 | def fun():
|
||||
84 |- _list = "built-in" # [RUF052]
|
||||
85 |- return _list
|
||||
84 |+ list_ = "built-in" # [RUF052]
|
||||
85 |+ return list_
|
||||
86 86 |
|
||||
87 87 | x = "global"
|
||||
88 88 |
|
||||
|
||||
RUF052.py:91:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
89 | def fun():
|
||||
90 | global x
|
||||
91 | _x = "shadows global" # [RUF052]
|
||||
| ^^ RUF052
|
||||
92 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
88 88 |
|
||||
89 89 | def fun():
|
||||
90 90 | global x
|
||||
91 |- _x = "shadows global" # [RUF052]
|
||||
92 |- return _x
|
||||
91 |+ x_ = "shadows global" # [RUF052]
|
||||
92 |+ return x_
|
||||
93 93 |
|
||||
94 94 | def foo():
|
||||
95 95 | x = "outer"
|
||||
|
||||
RUF052.py:98:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
96 | def bar():
|
||||
97 | nonlocal x
|
||||
98 | _x = "shadows nonlocal" # [RUF052]
|
||||
| ^^ RUF052
|
||||
99 | return _x
|
||||
100 | bar()
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
95 95 | x = "outer"
|
||||
96 96 | def bar():
|
||||
97 97 | nonlocal x
|
||||
98 |- _x = "shadows nonlocal" # [RUF052]
|
||||
99 |- return _x
|
||||
98 |+ x_ = "shadows nonlocal" # [RUF052]
|
||||
99 |+ return x_
|
||||
100 100 | bar()
|
||||
101 101 | return x
|
||||
102 102 |
|
||||
|
||||
RUF052.py:105:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
103 | def fun():
|
||||
104 | x = "local"
|
||||
105 | _x = "shadows local" # [RUF052]
|
||||
| ^^ RUF052
|
||||
106 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
102 102 |
|
||||
103 103 | def fun():
|
||||
104 104 | x = "local"
|
||||
105 |- _x = "shadows local" # [RUF052]
|
||||
106 |- return _x
|
||||
105 |+ x_ = "shadows local" # [RUF052]
|
||||
106 |+ return x_
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
RUF052.py:77:9: RUF052 [*] Local dummy variable `_var` is accessed
|
||||
|
|
||||
75 | class Class_:
|
||||
76 | def fun(self):
|
||||
77 | _var = "method variable" # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
78 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
ℹ Safe fix
|
||||
74 74 |
|
||||
75 75 | class Class_:
|
||||
76 76 | def fun(self):
|
||||
77 |- _var = "method variable" # [RUF052]
|
||||
78 |- return _var
|
||||
77 |+ var = "method variable" # [RUF052]
|
||||
78 |+ return var
|
||||
79 79 |
|
||||
80 80 | def fun(_var): # [RUF052]
|
||||
81 81 | return _var
|
||||
|
||||
RUF052.py:80:9: RUF052 [*] Local dummy variable `_var` is accessed
|
||||
|
|
||||
78 | return _var
|
||||
79 |
|
||||
80 | def fun(_var): # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
81 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
ℹ Safe fix
|
||||
77 77 | _var = "method variable" # [RUF052]
|
||||
78 78 | return _var
|
||||
79 79 |
|
||||
80 |-def fun(_var): # [RUF052]
|
||||
81 |- return _var
|
||||
80 |+def fun(var): # [RUF052]
|
||||
81 |+ return var
|
||||
82 82 |
|
||||
83 83 | def fun():
|
||||
84 84 | _list = "built-in" # [RUF052]
|
||||
|
||||
RUF052.py:84:5: RUF052 [*] Local dummy variable `_list` is accessed
|
||||
|
|
||||
83 | def fun():
|
||||
84 | _list = "built-in" # [RUF052]
|
||||
| ^^^^^ RUF052
|
||||
85 | return _list
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
|
||||
ℹ Safe fix
|
||||
81 81 | return _var
|
||||
82 82 |
|
||||
83 83 | def fun():
|
||||
84 |- _list = "built-in" # [RUF052]
|
||||
85 |- return _list
|
||||
84 |+ list_ = "built-in" # [RUF052]
|
||||
85 |+ return list_
|
||||
86 86 |
|
||||
87 87 | x = "global"
|
||||
88 88 |
|
||||
|
||||
RUF052.py:91:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
89 | def fun():
|
||||
90 | global x
|
||||
91 | _x = "shadows global" # [RUF052]
|
||||
| ^^ RUF052
|
||||
92 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
88 88 |
|
||||
89 89 | def fun():
|
||||
90 90 | global x
|
||||
91 |- _x = "shadows global" # [RUF052]
|
||||
92 |- return _x
|
||||
91 |+ x_ = "shadows global" # [RUF052]
|
||||
92 |+ return x_
|
||||
93 93 |
|
||||
94 94 | def foo():
|
||||
95 95 | x = "outer"
|
||||
|
||||
RUF052.py:98:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
96 | def bar():
|
||||
97 | nonlocal x
|
||||
98 | _x = "shadows nonlocal" # [RUF052]
|
||||
| ^^ RUF052
|
||||
99 | return _x
|
||||
100 | bar()
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
95 95 | x = "outer"
|
||||
96 96 | def bar():
|
||||
97 97 | nonlocal x
|
||||
98 |- _x = "shadows nonlocal" # [RUF052]
|
||||
99 |- return _x
|
||||
98 |+ x_ = "shadows nonlocal" # [RUF052]
|
||||
99 |+ return x_
|
||||
100 100 | bar()
|
||||
101 101 | return x
|
||||
102 102 |
|
||||
|
||||
RUF052.py:105:5: RUF052 [*] Local dummy variable `_x` is accessed
|
||||
|
|
||||
103 | def fun():
|
||||
104 | x = "local"
|
||||
105 | _x = "shadows local" # [RUF052]
|
||||
| ^^ RUF052
|
||||
106 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
ℹ Safe fix
|
||||
102 102 |
|
||||
103 103 | def fun():
|
||||
104 104 | x = "local"
|
||||
105 |- _x = "shadows local" # [RUF052]
|
||||
106 |- return _x
|
||||
105 |+ x_ = "shadows local" # [RUF052]
|
||||
106 |+ return x_
|
|
@ -0,0 +1,121 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
RUF052.py:21:9: RUF052 Local dummy variable `arg` is accessed
|
||||
|
|
||||
19 | _valid_fun()
|
||||
20 |
|
||||
21 | def fun(arg):
|
||||
| ^^^ RUF052
|
||||
22 | _valid_unused_var = arg
|
||||
23 | pass
|
||||
|
|
||||
|
||||
RUF052.py:50:18: RUF052 Local dummy variable `self` is accessed
|
||||
|
|
||||
48 | print(_valid_private_cls_attr)
|
||||
49 |
|
||||
50 | def __init__(self):
|
||||
| ^^^^ RUF052
|
||||
51 | self._valid_private_ins_attr = 2
|
||||
52 | print(self._valid_private_ins_attr)
|
||||
|
|
||||
|
||||
RUF052.py:54:23: RUF052 Local dummy variable `self` is accessed
|
||||
|
|
||||
52 | print(self._valid_private_ins_attr)
|
||||
53 |
|
||||
54 | def _valid_method(self):
|
||||
| ^^^^ RUF052
|
||||
55 | return self._valid_private_ins_attr
|
||||
|
|
||||
|
||||
RUF052.py:57:16: RUF052 Local dummy variable `arg` is accessed
|
||||
|
|
||||
55 | return self._valid_private_ins_attr
|
||||
56 |
|
||||
57 | def method(arg):
|
||||
| ^^^ RUF052
|
||||
58 | _valid_unused_var = arg
|
||||
59 | return
|
||||
|
|
||||
|
||||
RUF052.py:61:9: RUF052 Local dummy variable `x` is accessed
|
||||
|
|
||||
59 | return
|
||||
60 |
|
||||
61 | def fun(x):
|
||||
| ^ RUF052
|
||||
62 | _ = 1
|
||||
63 | __ = 2
|
||||
|
|
||||
|
||||
RUF052.py:77:9: RUF052 Local dummy variable `_var` is accessed
|
||||
|
|
||||
75 | class Class_:
|
||||
76 | def fun(self):
|
||||
77 | _var = "method variable" # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
78 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
RUF052.py:80:9: RUF052 Local dummy variable `_var` is accessed
|
||||
|
|
||||
78 | return _var
|
||||
79 |
|
||||
80 | def fun(_var): # [RUF052]
|
||||
| ^^^^ RUF052
|
||||
81 | return _var
|
||||
|
|
||||
= help: Remove leading underscores
|
||||
|
||||
RUF052.py:84:5: RUF052 Local dummy variable `_list` is accessed
|
||||
|
|
||||
83 | def fun():
|
||||
84 | _list = "built-in" # [RUF052]
|
||||
| ^^^^^ RUF052
|
||||
85 | return _list
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
|
||||
RUF052.py:91:5: RUF052 Local dummy variable `_x` is accessed
|
||||
|
|
||||
89 | def fun():
|
||||
90 | global x
|
||||
91 | _x = "shadows global" # [RUF052]
|
||||
| ^^ RUF052
|
||||
92 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052.py:95:3: RUF052 Local dummy variable `x` is accessed
|
||||
|
|
||||
94 | def foo():
|
||||
95 | x = "outer"
|
||||
| ^ RUF052
|
||||
96 | def bar():
|
||||
97 | nonlocal x
|
||||
|
|
||||
|
||||
RUF052.py:98:5: RUF052 Local dummy variable `_x` is accessed
|
||||
|
|
||||
96 | def bar():
|
||||
97 | nonlocal x
|
||||
98 | _x = "shadows nonlocal" # [RUF052]
|
||||
| ^^ RUF052
|
||||
99 | return _x
|
||||
100 | bar()
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052.py:105:5: RUF052 Local dummy variable `_x` is accessed
|
||||
|
|
||||
103 | def fun():
|
||||
104 | x = "local"
|
||||
105 | _x = "shadows local" # [RUF052]
|
||||
| ^^ RUF052
|
||||
106 | return _x
|
||||
|
|
||||
= help: Prefer using trailing underscores to avoid shadowing a variable
|
|
@ -325,9 +325,15 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
|
||||
/// Return `true` if `member` is an "available" symbol, i.e., a symbol that has not been bound
|
||||
/// in the current scope, or in any containing scope.
|
||||
/// in the current scope currently being visited, or in any containing scope.
|
||||
pub fn is_available(&self, member: &str) -> bool {
|
||||
self.lookup_symbol(member)
|
||||
self.is_available_in_scope(member, self.scope_id)
|
||||
}
|
||||
|
||||
/// Return `true` if `member` is an "available" symbol in a given scope, i.e.,
|
||||
/// a symbol that has not been bound in that current scope, or in any containing scope.
|
||||
pub fn is_available_in_scope(&self, member: &str, scope_id: ScopeId) -> bool {
|
||||
self.lookup_symbol_in_scope(member, scope_id, false)
|
||||
.map(|binding_id| &self.bindings[binding_id])
|
||||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
}
|
||||
|
@ -620,10 +626,22 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Lookup a symbol in the current scope. This is a carbon copy of [`Self::resolve_load`], but
|
||||
/// doesn't add any read references to the resolved symbol.
|
||||
/// Lookup a symbol in the current scope.
|
||||
pub fn lookup_symbol(&self, symbol: &str) -> Option<BindingId> {
|
||||
if self.in_forward_reference() {
|
||||
self.lookup_symbol_in_scope(symbol, self.scope_id, self.in_forward_reference())
|
||||
}
|
||||
|
||||
/// Lookup a symbol in a certain scope
|
||||
///
|
||||
/// This is a carbon copy of [`Self::resolve_load`], but
|
||||
/// doesn't add any read references to the resolved symbol.
|
||||
pub fn lookup_symbol_in_scope(
|
||||
&self,
|
||||
symbol: &str,
|
||||
scope_id: ScopeId,
|
||||
in_forward_reference: bool,
|
||||
) -> Option<BindingId> {
|
||||
if in_forward_reference {
|
||||
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
||||
if !self.bindings[binding_id].is_unbound() {
|
||||
return Some(binding_id);
|
||||
|
@ -633,7 +651,7 @@ impl<'a> SemanticModel<'a> {
|
|||
|
||||
let mut seen_function = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// See: https://github.com/python/cpython/blob/9d692841691590c25e6cf5b2250a594d3bf54825/Lib/keyword.py#L18
|
||||
pub(crate) fn is_keyword(name: &str) -> bool {
|
||||
pub fn is_keyword(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"False"
|
||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3845,6 +3845,7 @@
|
|||
"RUF041",
|
||||
"RUF048",
|
||||
"RUF05",
|
||||
"RUF052",
|
||||
"RUF055",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue