mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-03 13:14:34 +00:00
Only validate __all__ bindings for global scope (#2738)
This commit is contained in:
parent
0377834f9f
commit
d2b09d77c5
6 changed files with 74 additions and 37 deletions
7
crates/ruff/resources/test/fixtures/pyflakes/F822_2.py
vendored
Normal file
7
crates/ruff/resources/test/fixtures/pyflakes/F822_2.py
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
__all__ = ["foo"]
|
||||||
|
|
||||||
|
foo = 1
|
||||||
|
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
pass
|
||||||
|
|
@ -4647,8 +4647,10 @@ impl<'a> Checker<'a> {
|
||||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||||
// used at runtime, then by default, we avoid flagging any other
|
// used at runtime, then by default, we avoid flagging any other
|
||||||
// imports from that model as typing-only.
|
// imports from that model as typing-only.
|
||||||
let runtime_imports: Vec<Vec<&Binding>> = if !self.settings.flake8_type_checking.strict
|
let runtime_imports: Vec<Vec<&Binding>> = if self.settings.flake8_type_checking.strict {
|
||||||
&& (self
|
vec![]
|
||||||
|
} else {
|
||||||
|
if self
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
|
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
|
||||||
|
|
@ -4663,29 +4665,41 @@ impl<'a> Checker<'a> {
|
||||||
|| self
|
|| self
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::TypingOnlyStandardLibraryImport))
|
.enabled(&Rule::TypingOnlyStandardLibraryImport)
|
||||||
{
|
{
|
||||||
self.scopes
|
self.scopes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|scope| {
|
.map(|scope| {
|
||||||
scope
|
scope
|
||||||
.bindings
|
.bindings
|
||||||
.values()
|
.values()
|
||||||
.map(|index| &self.bindings[*index])
|
.map(|index| &self.bindings[*index])
|
||||||
.filter(|binding| {
|
.filter(|binding| {
|
||||||
flake8_type_checking::helpers::is_valid_runtime_import(binding)
|
flake8_type_checking::helpers::is_valid_runtime_import(binding)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||||
for (index, stack) in self.dead_scopes.iter().rev() {
|
for (index, stack) in self.dead_scopes.iter().rev() {
|
||||||
let scope = &self.scopes[*index];
|
let scope = &self.scopes[*index];
|
||||||
|
|
||||||
|
// F822
|
||||||
|
if *index == GLOBAL_SCOPE_INDEX {
|
||||||
|
if self.settings.rules.enabled(&Rule::UndefinedExport) {
|
||||||
|
if let Some((names, range)) = &all_names {
|
||||||
|
diagnostics.extend(pyflakes::rules::undefined_export(
|
||||||
|
names, range, self.path, scope,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PLW0602
|
// PLW0602
|
||||||
if self
|
if self
|
||||||
.settings
|
.settings
|
||||||
|
|
@ -4714,23 +4728,6 @@ impl<'a> Checker<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::UndefinedExport) {
|
|
||||||
if !scope.import_starred && !self.path.ends_with("__init__.py") {
|
|
||||||
if let Some((names, range)) = &all_names {
|
|
||||||
for &name in names {
|
|
||||||
if !scope.bindings.contains_key(name) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
pyflakes::rules::UndefinedExport {
|
|
||||||
name: name.to_string(),
|
|
||||||
},
|
|
||||||
*range,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for any bindings that were redefined in another scope, and remain
|
// Look for any bindings that were redefined in another scope, and remain
|
||||||
// unused. Note that we only store references in `redefinitions` if
|
// unused. Note that we only store references in `redefinitions` if
|
||||||
// the bindings are in different scopes.
|
// the bindings are in different scopes.
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ mod tests {
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")]
|
#[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"); "F822_0")]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"); "F822_0")]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"); "F822_1")]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"); "F822_1")]
|
||||||
|
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"); "F822_2")]
|
||||||
#[test_case(Rule::UndefinedLocal, Path::new("F823.py"); "F823")]
|
#[test_case(Rule::UndefinedLocal, Path::new("F823.py"); "F823")]
|
||||||
#[test_case(Rule::UnusedVariable, Path::new("F841_0.py"); "F841_0")]
|
#[test_case(Rule::UnusedVariable, Path::new("F841_0.py"); "F841_0")]
|
||||||
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"); "F841_1")]
|
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"); "F841_1")]
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ pub(crate) use strings::{
|
||||||
StringDotFormatExtraPositionalArguments, StringDotFormatInvalidFormat,
|
StringDotFormatExtraPositionalArguments, StringDotFormatInvalidFormat,
|
||||||
StringDotFormatMissingArguments, StringDotFormatMixingAutomatic,
|
StringDotFormatMissingArguments, StringDotFormatMixingAutomatic,
|
||||||
};
|
};
|
||||||
pub use undefined_export::UndefinedExport;
|
pub use undefined_export::{undefined_export, UndefinedExport};
|
||||||
pub use undefined_local::{undefined_local, UndefinedLocal};
|
pub use undefined_local::{undefined_local, UndefinedLocal};
|
||||||
pub use undefined_name::UndefinedName;
|
pub use undefined_name::UndefinedName;
|
||||||
pub use unused_annotation::{unused_annotation, UnusedAnnotation};
|
pub use unused_annotation::{unused_annotation, UnusedAnnotation};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
use crate::ast::types::{Range, Scope};
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
use ruff_macros::{define_violation, derive_message_formats};
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::violation::Violation;
|
use crate::violation::Violation;
|
||||||
|
|
||||||
|
|
@ -14,3 +17,26 @@ impl Violation for UndefinedExport {
|
||||||
format!("Undefined name `{name}` in `__all__`")
|
format!("Undefined name `{name}` in `__all__`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// F822
|
||||||
|
pub fn undefined_export(
|
||||||
|
names: &[&str],
|
||||||
|
range: &Range,
|
||||||
|
path: &Path,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Vec<Diagnostic> {
|
||||||
|
let mut diagnostics = Vec::new();
|
||||||
|
if !scope.import_starred && !path.ends_with("__init__.py") {
|
||||||
|
for name in names {
|
||||||
|
if !scope.bindings.contains_key(name) {
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
UndefinedExport {
|
||||||
|
name: (*name).to_string(),
|
||||||
|
},
|
||||||
|
*range,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diagnostics
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue