Only validate __all__ bindings for global scope (#2738)

This commit is contained in:
Charlie Marsh 2023-02-10 15:16:21 -05:00 committed by GitHub
parent 0377834f9f
commit d2b09d77c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 37 deletions

View file

@ -0,0 +1,7 @@
__all__ = ["foo"]
foo = 1
def bar():
pass

View file

@ -4647,8 +4647,10 @@ impl<'a> Checker<'a> {
// Identify any valid runtime imports. If a module is imported at runtime, and
// used at runtime, then by default, we avoid flagging any other
// imports from that model as typing-only.
let runtime_imports: Vec<Vec<&Binding>> = if !self.settings.flake8_type_checking.strict
&& (self
let runtime_imports: Vec<Vec<&Binding>> = if self.settings.flake8_type_checking.strict {
vec![]
} else {
if self
.settings
.rules
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
@ -4663,29 +4665,41 @@ impl<'a> Checker<'a> {
|| self
.settings
.rules
.enabled(&Rule::TypingOnlyStandardLibraryImport))
{
self.scopes
.iter()
.map(|scope| {
scope
.bindings
.values()
.map(|index| &self.bindings[*index])
.filter(|binding| {
flake8_type_checking::helpers::is_valid_runtime_import(binding)
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
} else {
vec![]
.enabled(&Rule::TypingOnlyStandardLibraryImport)
{
self.scopes
.iter()
.map(|scope| {
scope
.bindings
.values()
.map(|index| &self.bindings[*index])
.filter(|binding| {
flake8_type_checking::helpers::is_valid_runtime_import(binding)
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
} else {
vec![]
}
};
let mut diagnostics: Vec<Diagnostic> = vec![];
for (index, stack) in self.dead_scopes.iter().rev() {
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
if self
.settings
@ -4714,23 +4728,6 @@ impl<'a> Checker<'a> {
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
// unused. Note that we only store references in `redefinitions` if
// the bindings are in different scopes.

View file

@ -102,6 +102,7 @@ mod tests {
#[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_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::UnusedVariable, Path::new("F841_0.py"); "F841_0")]
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"); "F841_1")]

View file

@ -36,7 +36,7 @@ pub(crate) use strings::{
StringDotFormatExtraPositionalArguments, StringDotFormatInvalidFormat,
StringDotFormatMissingArguments, StringDotFormatMixingAutomatic,
};
pub use undefined_export::UndefinedExport;
pub use undefined_export::{undefined_export, UndefinedExport};
pub use undefined_local::{undefined_local, UndefinedLocal};
pub use undefined_name::UndefinedName;
pub use unused_annotation::{unused_annotation, UnusedAnnotation};

View file

@ -1,4 +1,7 @@
use crate::ast::types::{Range, Scope};
use crate::registry::Diagnostic;
use ruff_macros::{define_violation, derive_message_formats};
use std::path::Path;
use crate::violation::Violation;
@ -14,3 +17,26 @@ impl Violation for UndefinedExport {
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
}

View file

@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
expression: diagnostics
---
[]