diff --git a/README.md b/README.md index e8a578526c..f9ba26e080 100644 --- a/README.md +++ b/README.md @@ -1360,6 +1360,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI. | Code | Name | Message | Fix | | ---- | ---- | ------- | --- | +| PLE0100 | yield-in-init | `__init__` method is a generator | | | PLE0117 | nonlocal-without-binding | Nonlocal name `{name}` found without binding | | | PLE0118 | used-prior-global-declaration | Name `{name}` is used prior to global declaration on line {line} | | | PLE0604 | invalid-all-object | Invalid object in `__all__`, must contain only strings | | diff --git a/crates/ruff/resources/test/fixtures/pylint/yield_in_init.py b/crates/ruff/resources/test/fixtures/pylint/yield_in_init.py new file mode 100644 index 0000000000..8af5b2d0aa --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/yield_in_init.py @@ -0,0 +1,17 @@ +def a(): + yield + +def __init__(): + yield + +class A: + def __init__(self): + yield + + +class B: + def __init__(self): + yield from self.gen() + + def gen(self): + yield 5 diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index eab0aeb7e8..c855f3878b 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -2790,11 +2790,17 @@ where if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { pyflakes::rules::yield_outside_function(self, expr); } + if self.settings.rules.enabled(&Rule::YieldInInit) { + pylint::rules::yield_in_init(self, expr); + } } ExprKind::YieldFrom { .. } => { if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { pyflakes::rules::yield_outside_function(self, expr); } + if self.settings.rules.enabled(&Rule::YieldInInit) { + pylint::rules::yield_in_init(self, expr); + } } ExprKind::Await { .. } => { if self.settings.rules.enabled(&Rule::YieldOutsideFunction) { diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 3b4419023b..901f62153a 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -125,6 +125,7 @@ ruff_macros::define_rule_mapping!( F842 => rules::pyflakes::rules::UnusedAnnotation, F901 => rules::pyflakes::rules::RaiseNotImplemented, // pylint + PLE0100 => rules::pylint::rules::YieldInInit, PLE0604 => rules::pylint::rules::InvalidAllObject, PLE0605 => rules::pylint::rules::InvalidAllFormat, PLE1307 => rules::pylint::rules::BadStringFormatType, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 1ab336561b..7655dca7c9 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -44,6 +44,7 @@ mod tests { #[test_case(Rule::BadStringFormatType, Path::new("bad_string_format_type.py"); "PLE1307")] #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")] #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")] + #[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index 09425dd882..494e64dc74 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -24,6 +24,7 @@ pub use used_prior_global_declaration::{ }; pub use useless_else_on_loop::{useless_else_on_loop, UselessElseOnLoop}; pub use useless_import_alias::{useless_import_alias, UselessImportAlias}; +pub use yield_in_init::{yield_in_init, YieldInInit}; mod await_outside_async; mod bad_str_strip_call; @@ -47,3 +48,4 @@ mod use_from_import; mod used_prior_global_declaration; mod useless_else_on_loop; mod useless_import_alias; +mod yield_in_init; diff --git a/crates/ruff/src/rules/pylint/rules/yield_in_init.rs b/crates/ruff/src/rules/pylint/rules/yield_in_init.rs new file mode 100644 index 0000000000..6b2028379f --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/yield_in_init.rs @@ -0,0 +1,43 @@ +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::Expr; + +use crate::{ + ast::types::{FunctionDef, Range, ScopeKind}, + checkers::ast::Checker, + registry::Diagnostic, + violation::Violation, +}; + +define_violation!( + pub struct YieldInInit; +); + +impl Violation for YieldInInit { + #[derive_message_formats] + fn message(&self) -> String { + format!("`__init__` method is a generator") + } +} + +/// PLE0100 +pub fn yield_in_init(checker: &mut Checker, expr: &Expr) { + let parent_scope_is_class: Option = + checker + .current_scopes() + .nth(1) + .and_then(|scope| match scope.kind { + ScopeKind::Class(..) => Some(true), + _ => None, + }); + + let current_scope_is_init = match checker.current_scope().kind { + ScopeKind::Function(FunctionDef { name, .. }) => Some(name == "__init__"), + _ => None, + }; + + if parent_scope_is_class == Some(true) && current_scope_is_init == Some(true) { + checker + .diagnostics + .push(Diagnostic::new(YieldInInit, Range::from_located(expr))); + } +} diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0100_yield_in_init.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0100_yield_in_init.py.snap new file mode 100644 index 0000000000..dadaf449aa --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE0100_yield_in_init.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + YieldInInit: ~ + location: + row: 9 + column: 8 + end_location: + row: 9 + column: 13 + fix: ~ + parent: ~ +- kind: + YieldInInit: ~ + location: + row: 14 + column: 8 + end_location: + row: 14 + column: 29 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index cf2830375f..7bebf4c233 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1687,6 +1687,8 @@ "PLE", "PLE0", "PLE01", + "PLE010", + "PLE0100", "PLE011", "PLE0117", "PLE0118",