From 2bc16eb4e30fe1b921ed238edc3776271e72da22 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 7 Feb 2023 18:35:57 +0200 Subject: [PATCH] flake8-annotations: add ignore-fully-untyped (#2128) This PR adds a configuration option to inhibit ANN* violations for functions that have no other annotations either, for easier gradual typing of a large codebase. --- README.md | 19 + .../ignore_fully_untyped.py | 44 ++ .../suppress_none_returning.py | 5 + crates/ruff/src/checkers/ast.rs | 7 +- .../ruff/src/rules/flake8_annotations/mod.rs | 52 +- .../src/rules/flake8_annotations/rules.rs | 488 ++++++++++-------- .../src/rules/flake8_annotations/settings.rs | 12 + ...otations__tests__ignore_fully_untyped.snap | 60 +++ ..._annotations__tests__mypy_init_return.snap | 11 +- ...tions__tests__suppress_none_returning.snap | 13 +- ruff.schema.json | 7 + 11 files changed, 468 insertions(+), 250 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py create mode 100644 crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__ignore_fully_untyped.snap diff --git a/README.md b/README.md index e1fbaf66a3..df17c5bb83 100644 --- a/README.md +++ b/README.md @@ -2663,6 +2663,25 @@ allow-star-arg-any = true --- +#### [`ignore-fully-untyped`](#ignore-fully-untyped) + +Whether to suppress `ANN*` rules for any declaration +that hasn't been typed at all. +This makes it easier to gradually add types to a codebase. + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff.flake8-annotations] +ignore-fully-untyped = true +``` + +--- + #### [`mypy-init-return`](#mypy-init-return) Whether to allow the omission of a return type hint for `__init__` if at diff --git a/crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py b/crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py new file mode 100644 index 0000000000..f7cadfe7e5 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py @@ -0,0 +1,44 @@ +"""Test case expected to be run with `ignore_fully_untyped = True`.""" + + +def ok_fully_untyped_1(a, b): + pass + + +def ok_fully_untyped_2(): + pass + + +def ok_fully_typed_1(a: int, b: int) -> int: + pass + + +def ok_fully_typed_2() -> int: + pass + + +def ok_fully_typed_3(a: int, *args: str, **kwargs: str) -> int: + pass + + +def error_partially_typed_1(a: int, b): + pass + + +def error_partially_typed_2(a: int, b) -> int: + pass + + +def error_partially_typed_3(a: int, b: int): + pass + + +class X: + def ok_untyped_method_with_arg(self, a): + pass + + def ok_untyped_method(self): + pass + + def error_typed_self(self: X): + pass diff --git a/crates/ruff/resources/test/fixtures/flake8_annotations/suppress_none_returning.py b/crates/ruff/resources/test/fixtures/flake8_annotations/suppress_none_returning.py index 80e016a636..f82e906455 100644 --- a/crates/ruff/resources/test/fixtures/flake8_annotations/suppress_none_returning.py +++ b/crates/ruff/resources/test/fixtures/flake8_annotations/suppress_none_returning.py @@ -53,3 +53,8 @@ def foo(): return True else: return + + +# Error (on the argument, but not the return type) +def foo(a): + a = 2 + 2 diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index 0e85d62793..ac8db4f0ec 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -5079,7 +5079,12 @@ impl<'a> Checker<'a> { &overloaded_name, ) }) { - flake8_annotations::rules::definition(self, &definition, &visibility); + self.diagnostics + .extend(flake8_annotations::rules::definition( + self, + &definition, + &visibility, + )); } overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition); } diff --git a/crates/ruff/src/rules/flake8_annotations/mod.rs b/crates/ruff/src/rules/flake8_annotations/mod.rs index 43a56418eb..2517b6ed1c 100644 --- a/crates/ruff/src/rules/flake8_annotations/mod.rs +++ b/crates/ruff/src/rules/flake8_annotations/mod.rs @@ -39,16 +39,42 @@ mod tests { Ok(()) } + #[test] + fn ignore_fully_untyped() -> Result<()> { + let diagnostics = test_path( + Path::new("flake8_annotations/ignore_fully_untyped.py"), + &Settings { + flake8_annotations: super::settings::Settings { + ignore_fully_untyped: true, + ..Default::default() + }, + ..Settings::for_rules(vec![ + Rule::MissingTypeFunctionArgument, + Rule::MissingTypeArgs, + Rule::MissingTypeKwargs, + Rule::MissingTypeSelf, + Rule::MissingTypeCls, + Rule::MissingReturnTypePublicFunction, + Rule::MissingReturnTypePrivateFunction, + Rule::MissingReturnTypeSpecialMethod, + Rule::MissingReturnTypeStaticMethod, + Rule::MissingReturnTypeClassMethod, + Rule::DynamicallyTypedExpression, + ]) + }, + )?; + assert_yaml_snapshot!(diagnostics); + Ok(()) + } + #[test] fn suppress_dummy_args() -> Result<()> { let diagnostics = test_path( Path::new("flake8_annotations/suppress_dummy_args.py"), &Settings { flake8_annotations: super::settings::Settings { - mypy_init_return: false, suppress_dummy_args: true, - suppress_none_returning: false, - allow_star_arg_any: false, + ..Default::default() }, ..Settings::for_rules(vec![ Rule::MissingTypeFunctionArgument, @@ -70,9 +96,7 @@ mod tests { &Settings { flake8_annotations: super::settings::Settings { mypy_init_return: true, - suppress_dummy_args: false, - suppress_none_returning: false, - allow_star_arg_any: false, + ..Default::default() }, ..Settings::for_rules(vec![ Rule::MissingReturnTypePublicFunction, @@ -83,7 +107,7 @@ mod tests { ]) }, )?; - assert_yaml_snapshot!(diagnostics); + insta::assert_yaml_snapshot!(diagnostics); Ok(()) } @@ -93,17 +117,21 @@ mod tests { Path::new("flake8_annotations/suppress_none_returning.py"), &Settings { flake8_annotations: super::settings::Settings { - mypy_init_return: false, - suppress_dummy_args: false, suppress_none_returning: true, - allow_star_arg_any: false, + ..Default::default() }, ..Settings::for_rules(vec![ + Rule::MissingTypeFunctionArgument, + Rule::MissingTypeArgs, + Rule::MissingTypeKwargs, + Rule::MissingTypeSelf, + Rule::MissingTypeCls, Rule::MissingReturnTypePublicFunction, Rule::MissingReturnTypePrivateFunction, Rule::MissingReturnTypeSpecialMethod, Rule::MissingReturnTypeStaticMethod, Rule::MissingReturnTypeClassMethod, + Rule::DynamicallyTypedExpression, ]) }, )?; @@ -117,10 +145,8 @@ mod tests { Path::new("flake8_annotations/allow_star_arg_any.py"), &Settings { flake8_annotations: super::settings::Settings { - mypy_init_return: false, - suppress_dummy_args: false, - suppress_none_returning: false, allow_star_arg_any: true, + ..Default::default() }, ..Settings::for_rules(vec![Rule::DynamicallyTypedExpression]) }, diff --git a/crates/ruff/src/rules/flake8_annotations/rules.rs b/crates/ruff/src/rules/flake8_annotations/rules.rs index 1cbe6c6ed4..c9d03de804 100644 --- a/crates/ruff/src/rules/flake8_annotations/rules.rs +++ b/crates/ruff/src/rules/flake8_annotations/rules.rs @@ -185,12 +185,16 @@ fn is_none_returning(body: &[Stmt]) -> bool { } /// ANN401 -fn check_dynamically_typed(checker: &mut Checker, annotation: &Expr, func: F) -where +fn check_dynamically_typed( + checker: &Checker, + annotation: &Expr, + func: F, + diagnostics: &mut Vec, +) where F: FnOnce() -> String, { if checker.match_typing_expr(annotation, "Any") { - checker.diagnostics.push(Diagnostic::new( + diagnostics.push(Diagnostic::new( DynamicallyTypedExpression { name: func() }, Range::from_located(annotation), )); @@ -198,273 +202,301 @@ where } /// Generate flake8-annotation checks for a given `Definition`. -pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) { +pub fn definition( + checker: &Checker, + definition: &Definition, + visibility: &Visibility, +) -> Vec { // TODO(charlie): Consider using the AST directly here rather than `Definition`. // We could adhere more closely to `flake8-annotations` by defining public // vs. secret vs. protected. - match &definition.kind { - DefinitionKind::Module => {} - DefinitionKind::Package => {} - DefinitionKind::Class(_) => {} - DefinitionKind::NestedClass(_) => {} - DefinitionKind::Function(stmt) - | DefinitionKind::NestedFunction(stmt) - | DefinitionKind::Method(stmt) => { - let is_method = matches!(definition.kind, DefinitionKind::Method(_)); - let (name, args, returns, body) = match_function_def(stmt); - let mut has_any_typed_arg = false; + if let DefinitionKind::Function(stmt) + | DefinitionKind::NestedFunction(stmt) + | DefinitionKind::Method(stmt) = &definition.kind + { + let is_method = matches!(definition.kind, DefinitionKind::Method(_)); + let (name, args, returns, body) = match_function_def(stmt); + // Keep track of whether we've seen any typed arguments or return values. + let mut has_any_typed_arg = false; // Any argument has been typed? + let mut has_typed_return = false; // Return value has been typed? + let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument? - // ANN001, ANN401 - for arg in args - .args - .iter() - .chain(args.posonlyargs.iter()) - .chain(args.kwonlyargs.iter()) - .skip( - // If this is a non-static method, skip `cls` or `self`. - usize::from( - is_method - && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)), - ), - ) - { - // ANN401 for dynamically typed arguments - if let Some(annotation) = &arg.node.annotation { - has_any_typed_arg = true; - if checker - .settings - .rules - .enabled(&Rule::DynamicallyTypedExpression) - { - check_dynamically_typed(checker, annotation, || arg.node.arg.to_string()); - } - } else { - if !(checker.settings.flake8_annotations.suppress_dummy_args - && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) - { - if checker - .settings - .rules - .enabled(&Rule::MissingTypeFunctionArgument) - { - checker.diagnostics.push(Diagnostic::new( - MissingTypeFunctionArgument { - name: arg.node.arg.to_string(), - }, - Range::from_located(arg), - )); - } - } - } - } + // Temporary storage for diagnostics; we emit them at the end + // unless configured to suppress ANN* for declarations that are fully untyped. + let mut diagnostics = Vec::new(); - // ANN002, ANN401 - if let Some(arg) = &args.vararg { - if let Some(expr) = &arg.node.annotation { - has_any_typed_arg = true; - if !checker.settings.flake8_annotations.allow_star_arg_any { - if checker - .settings - .rules - .enabled(&Rule::DynamicallyTypedExpression) - { - let name = &arg.node.arg; - check_dynamically_typed(checker, expr, || format!("*{name}")); - } - } - } else { - if !(checker.settings.flake8_annotations.suppress_dummy_args - && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) - { - if checker.settings.rules.enabled(&Rule::MissingTypeArgs) { - checker.diagnostics.push(Diagnostic::new( - MissingTypeArgs { - name: arg.node.arg.to_string(), - }, - Range::from_located(arg), - )); - } - } - } - } - - // ANN003, ANN401 - if let Some(arg) = &args.kwarg { - if let Some(expr) = &arg.node.annotation { - has_any_typed_arg = true; - if !checker.settings.flake8_annotations.allow_star_arg_any { - if checker - .settings - .rules - .enabled(&Rule::DynamicallyTypedExpression) - { - let name = &arg.node.arg; - check_dynamically_typed(checker, expr, || format!("**{name}")); - } - } - } else { - if !(checker.settings.flake8_annotations.suppress_dummy_args - && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) - { - if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) { - checker.diagnostics.push(Diagnostic::new( - MissingTypeKwargs { - name: arg.node.arg.to_string(), - }, - Range::from_located(arg), - )); - } - } - } - } - - // ANN101, ANN102 - if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) { - if let Some(arg) = args.args.first() { - if arg.node.annotation.is_none() { - if visibility::is_classmethod(checker, cast::decorator_list(stmt)) { - if checker.settings.rules.enabled(&Rule::MissingTypeCls) { - checker.diagnostics.push(Diagnostic::new( - MissingTypeCls { - name: arg.node.arg.to_string(), - }, - Range::from_located(arg), - )); - } - } else { - if checker.settings.rules.enabled(&Rule::MissingTypeSelf) { - checker.diagnostics.push(Diagnostic::new( - MissingTypeSelf { - name: arg.node.arg.to_string(), - }, - Range::from_located(arg), - )); - } - } - } - } - } - - // ANN201, ANN202, ANN401 - if let Some(expr) = &returns { + // ANN001, ANN401 + for arg in args + .args + .iter() + .chain(args.posonlyargs.iter()) + .chain(args.kwonlyargs.iter()) + .skip( + // If this is a non-static method, skip `cls` or `self`. + usize::from( + is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)), + ), + ) + { + // ANN401 for dynamically typed arguments + if let Some(annotation) = &arg.node.annotation { + has_any_typed_arg = true; if checker .settings .rules .enabled(&Rule::DynamicallyTypedExpression) { - check_dynamically_typed(checker, expr, || name.to_string()); + check_dynamically_typed( + checker, + annotation, + || arg.node.arg.to_string(), + &mut diagnostics, + ); } } else { - // Allow omission of return annotation if the function only returns `None` - // (explicitly or implicitly). - if checker.settings.flake8_annotations.suppress_none_returning - && is_none_returning(body) + if !(checker.settings.flake8_annotations.suppress_dummy_args + && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) { - return; + if checker + .settings + .rules + .enabled(&Rule::MissingTypeFunctionArgument) + { + diagnostics.push(Diagnostic::new( + MissingTypeFunctionArgument { + name: arg.node.arg.to_string(), + }, + Range::from_located(arg), + )); + } } + } + } - if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) { + // ANN002, ANN401 + if let Some(arg) = &args.vararg { + if let Some(expr) = &arg.node.annotation { + has_any_typed_arg = true; + if !checker.settings.flake8_annotations.allow_star_arg_any { if checker .settings .rules - .enabled(&Rule::MissingReturnTypeClassMethod) + .enabled(&Rule::DynamicallyTypedExpression) { - checker.diagnostics.push(Diagnostic::new( - MissingReturnTypeClassMethod { - name: name.to_string(), - }, - helpers::identifier_range(stmt, checker.locator), - )); + let name = &arg.node.arg; + check_dynamically_typed( + checker, + expr, + || format!("*{name}"), + &mut diagnostics, + ); } - } else if is_method - && visibility::is_staticmethod(checker, cast::decorator_list(stmt)) + } + } else { + if !(checker.settings.flake8_annotations.suppress_dummy_args + && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) { - if checker - .settings - .rules - .enabled(&Rule::MissingReturnTypeStaticMethod) - { - checker.diagnostics.push(Diagnostic::new( - MissingReturnTypeStaticMethod { - name: name.to_string(), + if checker.settings.rules.enabled(&Rule::MissingTypeArgs) { + diagnostics.push(Diagnostic::new( + MissingTypeArgs { + name: arg.node.arg.to_string(), }, - helpers::identifier_range(stmt, checker.locator), + Range::from_located(arg), )); } - } else if is_method && visibility::is_init(cast::name(stmt)) { - // Allow omission of return annotation in `__init__` functions, as long as at - // least one argument is typed. + } + } + } + + // ANN003, ANN401 + if let Some(arg) = &args.kwarg { + if let Some(expr) = &arg.node.annotation { + has_any_typed_arg = true; + if !checker.settings.flake8_annotations.allow_star_arg_any { if checker .settings .rules - .enabled(&Rule::MissingReturnTypeSpecialMethod) + .enabled(&Rule::DynamicallyTypedExpression) { - if !(checker.settings.flake8_annotations.mypy_init_return - && has_any_typed_arg) - { - let mut diagnostic = Diagnostic::new( - MissingReturnTypeSpecialMethod { - name: name.to_string(), + let name = &arg.node.arg; + check_dynamically_typed( + checker, + expr, + || format!("**{name}"), + &mut diagnostics, + ); + } + } + } else { + if !(checker.settings.flake8_annotations.suppress_dummy_args + && checker.settings.dummy_variable_rgx.is_match(&arg.node.arg)) + { + if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) { + diagnostics.push(Diagnostic::new( + MissingTypeKwargs { + name: arg.node.arg.to_string(), + }, + Range::from_located(arg), + )); + } + } + } + } + + // ANN101, ANN102 + if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) { + if let Some(arg) = args.args.first() { + if arg.node.annotation.is_none() { + if visibility::is_classmethod(checker, cast::decorator_list(stmt)) { + if checker.settings.rules.enabled(&Rule::MissingTypeCls) { + diagnostics.push(Diagnostic::new( + MissingTypeCls { + name: arg.node.arg.to_string(), }, - helpers::identifier_range(stmt, checker.locator), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::add_return_none_annotation(checker.locator, stmt) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); + Range::from_located(arg), + )); + } + } else { + if checker.settings.rules.enabled(&Rule::MissingTypeSelf) { + diagnostics.push(Diagnostic::new( + MissingTypeSelf { + name: arg.node.arg.to_string(), + }, + Range::from_located(arg), + )); } } - } else if is_method && visibility::is_magic(cast::name(stmt)) { - if checker - .settings - .rules - .enabled(&Rule::MissingReturnTypeSpecialMethod) + } else { + has_typed_self_or_cls = true; + } + } + } + + // ANN201, ANN202, ANN401 + if let Some(expr) = &returns { + has_typed_return = true; + if checker + .settings + .rules + .enabled(&Rule::DynamicallyTypedExpression) + { + check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics); + } + } else if !( + // Allow omission of return annotation if the function only returns `None` + // (explicitly or implicitly). + checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body) + ) { + if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) { + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypeClassMethod) + { + diagnostics.push(Diagnostic::new( + MissingReturnTypeClassMethod { + name: name.to_string(), + }, + helpers::identifier_range(stmt, checker.locator), + )); + } + } else if is_method && visibility::is_staticmethod(checker, cast::decorator_list(stmt)) + { + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypeStaticMethod) + { + diagnostics.push(Diagnostic::new( + MissingReturnTypeStaticMethod { + name: name.to_string(), + }, + helpers::identifier_range(stmt, checker.locator), + )); + } + } else if is_method && visibility::is_init(cast::name(stmt)) { + // Allow omission of return annotation in `__init__` functions, as long as at + // least one argument is typed. + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypeSpecialMethod) + { + if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg) { - checker.diagnostics.push(Diagnostic::new( + let mut diagnostic = Diagnostic::new( MissingReturnTypeSpecialMethod { name: name.to_string(), }, helpers::identifier_range(stmt, checker.locator), - )); - } - } else { - match visibility { - Visibility::Public => { - if checker - .settings - .rules - .enabled(&Rule::MissingReturnTypePublicFunction) - { - checker.diagnostics.push(Diagnostic::new( - MissingReturnTypePublicFunction { - name: name.to_string(), - }, - helpers::identifier_range(stmt, checker.locator), - )); + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::add_return_none_annotation(checker.locator, stmt) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), } } - Visibility::Private => { - if checker - .settings - .rules - .enabled(&Rule::MissingReturnTypePrivateFunction) - { - checker.diagnostics.push(Diagnostic::new( - MissingReturnTypePrivateFunction { - name: name.to_string(), - }, - helpers::identifier_range(stmt, checker.locator), - )); - } + diagnostics.push(diagnostic); + } + } + } else if is_method && visibility::is_magic(cast::name(stmt)) { + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypeSpecialMethod) + { + diagnostics.push(Diagnostic::new( + MissingReturnTypeSpecialMethod { + name: name.to_string(), + }, + helpers::identifier_range(stmt, checker.locator), + )); + } + } else { + match visibility { + Visibility::Public => { + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypePublicFunction) + { + diagnostics.push(Diagnostic::new( + MissingReturnTypePublicFunction { + name: name.to_string(), + }, + helpers::identifier_range(stmt, checker.locator), + )); + } + } + Visibility::Private => { + if checker + .settings + .rules + .enabled(&Rule::MissingReturnTypePrivateFunction) + { + diagnostics.push(Diagnostic::new( + MissingReturnTypePrivateFunction { + name: name.to_string(), + }, + helpers::identifier_range(stmt, checker.locator), + )); } } } } } + // If settings say so, don't report any of the + // diagnostics gathered here if there were no type annotations at all. + if checker.settings.flake8_annotations.ignore_fully_untyped + && !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return) + { + vec![] + } else { + diagnostics + } + } else { + vec![] } } diff --git a/crates/ruff/src/rules/flake8_annotations/settings.rs b/crates/ruff/src/rules/flake8_annotations/settings.rs index 0789d90066..46387b4eea 100644 --- a/crates/ruff/src/rules/flake8_annotations/settings.rs +++ b/crates/ruff/src/rules/flake8_annotations/settings.rs @@ -49,6 +49,15 @@ pub struct Options { /// Whether to suppress `ANN401` for dynamically typed `*args` and /// `**kwargs` arguments. pub allow_star_arg_any: Option, + #[option( + default = "false", + value_type = "bool", + example = "ignore-fully-untyped = true" + )] + /// Whether to suppress `ANN*` rules for any declaration + /// that hasn't been typed at all. + /// This makes it easier to gradually add types to a codebase. + pub ignore_fully_untyped: Option, } #[derive(Debug, Default, Hash)] @@ -58,6 +67,7 @@ pub struct Settings { pub suppress_dummy_args: bool, pub suppress_none_returning: bool, pub allow_star_arg_any: bool, + pub ignore_fully_untyped: bool, } impl From for Settings { @@ -67,6 +77,7 @@ impl From for Settings { suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false), suppress_none_returning: options.suppress_none_returning.unwrap_or(false), allow_star_arg_any: options.allow_star_arg_any.unwrap_or(false), + ignore_fully_untyped: options.ignore_fully_untyped.unwrap_or(false), } } } @@ -78,6 +89,7 @@ impl From for Options { suppress_dummy_args: Some(settings.suppress_dummy_args), suppress_none_returning: Some(settings.suppress_none_returning), allow_star_arg_any: Some(settings.allow_star_arg_any), + ignore_fully_untyped: Some(settings.ignore_fully_untyped), } } } diff --git a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__ignore_fully_untyped.snap b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__ignore_fully_untyped.snap new file mode 100644 index 0000000000..b43a0bfdc4 --- /dev/null +++ b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__ignore_fully_untyped.snap @@ -0,0 +1,60 @@ +--- +source: crates/ruff/src/rules/flake8_annotations/mod.rs +expression: diagnostics +--- +- kind: + MissingReturnTypePublicFunction: + name: error_partially_typed_1 + location: + row: 24 + column: 4 + end_location: + row: 24 + column: 27 + fix: ~ + parent: ~ +- kind: + MissingTypeFunctionArgument: + name: b + location: + row: 24 + column: 36 + end_location: + row: 24 + column: 37 + fix: ~ + parent: ~ +- kind: + MissingTypeFunctionArgument: + name: b + location: + row: 28 + column: 36 + end_location: + row: 28 + column: 37 + fix: ~ + parent: ~ +- kind: + MissingReturnTypePublicFunction: + name: error_partially_typed_3 + location: + row: 32 + column: 4 + end_location: + row: 32 + column: 27 + fix: ~ + parent: ~ +- kind: + MissingReturnTypePublicFunction: + name: error_typed_self + location: + row: 43 + column: 8 + end_location: + row: 43 + column: 24 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__mypy_init_return.snap b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__mypy_init_return.snap index a46644766c..a134fd9184 100644 --- a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__mypy_init_return.snap +++ b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__mypy_init_return.snap @@ -1,5 +1,5 @@ --- -source: src/rules/flake8_annotations/mod.rs +source: crates/ruff/src/rules/flake8_annotations/mod.rs expression: diagnostics --- - kind: @@ -12,8 +12,7 @@ expression: diagnostics row: 5 column: 16 fix: - content: - - " -> None" + content: " -> None" location: row: 5 column: 22 @@ -31,8 +30,7 @@ expression: diagnostics row: 11 column: 16 fix: - content: - - " -> None" + content: " -> None" location: row: 11 column: 27 @@ -61,8 +59,7 @@ expression: diagnostics row: 47 column: 16 fix: - content: - - " -> None" + content: " -> None" location: row: 47 column: 28 diff --git a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__suppress_none_returning.snap b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__suppress_none_returning.snap index d182df20a8..86c5371be1 100644 --- a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__suppress_none_returning.snap +++ b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__suppress_none_returning.snap @@ -1,5 +1,5 @@ --- -source: src/rules/flake8_annotations/mod.rs +source: crates/ruff/src/rules/flake8_annotations/mod.rs expression: diagnostics --- - kind: @@ -24,4 +24,15 @@ expression: diagnostics column: 7 fix: ~ parent: ~ +- kind: + MissingTypeFunctionArgument: + name: a + location: + row: 59 + column: 8 + end_location: + row: 59 + column: 9 + fix: ~ + parent: ~ diff --git a/ruff.schema.json b/ruff.schema.json index ef681ac176..a9f92e7442 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -541,6 +541,13 @@ "null" ] }, + "ignore-fully-untyped": { + "description": "Whether to suppress `ANN*` rules for any declaration that hasn't been typed at all. This makes it easier to gradually add types to a codebase.", + "type": [ + "boolean", + "null" + ] + }, "mypy-init-return": { "description": "Whether to allow the omission of a return type hint for `__init__` if at least one argument is annotated.", "type": [