diff --git a/resources/test/src/F704.py b/resources/test/src/F704.py new file mode 100644 index 0000000000..f55af4da42 --- /dev/null +++ b/resources/test/src/F704.py @@ -0,0 +1,10 @@ +def f() -> int: + yield 1 + + +class Foo: + yield 2 + + +yield 3 +yield from 3 diff --git a/resources/test/src/pyproject.toml b/resources/test/src/pyproject.toml index 92a284aaeb..21574e3fc2 100644 --- a/resources/test/src/pyproject.toml +++ b/resources/test/src/pyproject.toml @@ -7,6 +7,7 @@ select = [ "F403", "F541", "F634", + "F704", "F706", "F821", "F831", diff --git a/src/check_ast.rs b/src/check_ast.rs index 52054c4eca..76474811f1 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -349,6 +349,21 @@ impl Visitor for Checker<'_> { | ExprKind::DictComp { .. } | ExprKind::SetComp { .. } => self.push_scope(Scope::new(Generator)), ExprKind::Lambda { .. } => self.push_scope(Scope::new(Function)), + ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } => { + let scope = self.scopes.last().expect("No current scope found."); + if self + .settings + .select + .contains(CheckKind::YieldOutsideFunction.code()) + && matches!(scope.kind, ScopeKind::Class) + || matches!(scope.kind, ScopeKind::Module) + { + self.checks.push(Check { + kind: CheckKind::YieldOutsideFunction, + location: expr.location, + }); + } + } ExprKind::JoinedStr { values } => { if !self.in_f_string && self diff --git a/src/checks.rs b/src/checks.rs index 6583cd2bea..e5f6b77551 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -12,6 +12,7 @@ pub enum CheckCode { F403, F541, F634, + F704, F706, F821, F831, @@ -30,6 +31,7 @@ impl FromStr for CheckCode { "F403" => Ok(CheckCode::F403), "F541" => Ok(CheckCode::F541), "F634" => Ok(CheckCode::F634), + "F704" => Ok(CheckCode::F704), "F706" => Ok(CheckCode::F706), "F821" => Ok(CheckCode::F821), "F831" => Ok(CheckCode::F831), @@ -49,6 +51,7 @@ impl CheckCode { CheckCode::F403 => "F403", CheckCode::F541 => "F541", CheckCode::F634 => "F634", + CheckCode::F704 => "F704", CheckCode::F706 => "F706", CheckCode::F821 => "F821", CheckCode::F831 => "F831", @@ -66,6 +69,7 @@ impl CheckCode { CheckCode::F403 => &LintSource::AST, CheckCode::F541 => &LintSource::AST, CheckCode::F634 => &LintSource::AST, + CheckCode::F704 => &LintSource::AST, CheckCode::F706 => &LintSource::AST, CheckCode::F821 => &LintSource::AST, CheckCode::F831 => &LintSource::AST, @@ -90,6 +94,7 @@ pub enum CheckKind { ImportStarUsage, LineTooLong, RaiseNotImplemented, + YieldOutsideFunction, ReturnOutsideFunction, UndefinedName(String), UndefinedLocal(String), @@ -107,6 +112,7 @@ impl CheckKind { CheckKind::ImportStarUsage => &CheckCode::F403, CheckKind::LineTooLong => &CheckCode::E501, CheckKind::RaiseNotImplemented => &CheckCode::F901, + CheckKind::YieldOutsideFunction => &CheckCode::F704, CheckKind::ReturnOutsideFunction => &CheckCode::F706, CheckKind::UndefinedName(_) => &CheckCode::F821, CheckKind::UndefinedLocal(_) => &CheckCode::F832, @@ -132,6 +138,9 @@ impl CheckKind { CheckKind::RaiseNotImplemented => { "'raise NotImplemented' should be 'raise NotImplementedError".to_string() } + CheckKind::YieldOutsideFunction => { + "a `yield` or `yield from` statement outside of a function/method".to_string() + } CheckKind::ReturnOutsideFunction => { "a `return` statement outside of a function/method".to_string() } diff --git a/src/linter.rs b/src/linter.rs index 76633073b3..5e7d8bf39b 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -222,6 +222,42 @@ mod tests { Ok(()) } + #[test] + fn f704() -> Result<()> { + let actual = check_path( + &Path::new("./resources/test/src/F704.py"), + &settings::Settings { + line_length: 88, + exclude: vec![], + select: BTreeSet::from([CheckCode::F704]), + }, + &cache::Mode::None, + )?; + let expected = vec![ + Message { + kind: CheckKind::YieldOutsideFunction, + location: Location::new(6, 5), + filename: "./resources/test/src/F704.py".to_string(), + }, + Message { + kind: CheckKind::YieldOutsideFunction, + location: Location::new(9, 1), + filename: "./resources/test/src/F704.py".to_string(), + }, + Message { + kind: CheckKind::YieldOutsideFunction, + location: Location::new(10, 1), + filename: "./resources/test/src/F704.py".to_string(), + }, + ]; + assert_eq!(actual.len(), expected.len()); + for i in 0..actual.len() { + assert_eq!(actual[i], expected[i]); + } + + Ok(()) + } + #[test] fn f706() -> Result<()> { let actual = check_path( diff --git a/src/pyproject.rs b/src/pyproject.rs index d8c59d243e..6895a20d41 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -235,6 +235,7 @@ other-attribute = 1 CheckCode::F403, CheckCode::F541, CheckCode::F634, + CheckCode::F704, CheckCode::F706, CheckCode::F821, CheckCode::F831,