diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index d91dcc25f9..faaf41595a 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -2301,7 +2301,9 @@ impl<'a> Checker<'a> { } } else { if self.enabled(Rule::UndefinedExport) { - if !self.path.ends_with("__init__.py") { + if self.settings.preview.is_enabled() + || !self.path.ends_with("__init__.py") + { self.diagnostics.push( Diagnostic::new( pyflakes::rules::UndefinedExport { diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 107cb96fb3..81bc61c1f1 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -212,6 +212,7 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))] + #[test_case(Rule::UndefinedExport, Path::new("__init__.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs index 6858f1124c..a922c43b3e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/undefined_export.rs @@ -13,6 +13,11 @@ use ruff_macros::{derive_message_formats, violation}; /// Including an undefined name in `__all__` is likely to raise `NameError` at /// runtime, when the module is imported. /// +/// In [preview], this rule will flag undefined names in `__init__.py` file, +/// even if those names implicitly refer to other modules in the package. Users +/// that rely on implicit exports should disable this rule in `__init__.py` +/// files via [`lint.per-file-ignores`]. +/// /// ## Example /// ```python /// from foo import bar @@ -31,6 +36,8 @@ use ruff_macros::{derive_message_formats, violation}; /// /// ## References /// - [Python documentation: `__all__`](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package) +/// +/// [preview]: https://docs.astral.sh/ruff/preview/ #[violation] pub struct UndefinedExport { pub name: String, diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F822___init__.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F822___init__.py.snap new file mode 100644 index 0000000000..d9761fcd78 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F822___init__.py.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +__init__.py:5:12: F822 Undefined name `a` in `__all__` + | +3 | print(__path__) +4 | +5 | __all__ = ["a", "b", "c"] + | ^^^ F822 + | + +__init__.py:5:17: F822 Undefined name `b` in `__all__` + | +3 | print(__path__) +4 | +5 | __all__ = ["a", "b", "c"] + | ^^^ F822 + | + +__init__.py:5:22: F822 Undefined name `c` in `__all__` + | +3 | print(__path__) +4 | +5 | __all__ = ["a", "b", "c"] + | ^^^ F822 + |