mirror of https://github.com/astral-sh/ruff
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.
This commit is contained in:
parent
4e36225145
commit
2bc16eb4e3
19
README.md
19
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)
|
#### [`mypy-init-return`](#mypy-init-return)
|
||||||
|
|
||||||
Whether to allow the omission of a return type hint for `__init__` if at
|
Whether to allow the omission of a return type hint for `__init__` if at
|
||||||
|
|
|
||||||
44
crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py
vendored
Normal file
44
crates/ruff/resources/test/fixtures/flake8_annotations/ignore_fully_untyped.py
vendored
Normal file
|
|
@ -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
|
||||||
|
|
@ -53,3 +53,8 @@ def foo():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Error (on the argument, but not the return type)
|
||||||
|
def foo(a):
|
||||||
|
a = 2 + 2
|
||||||
|
|
|
||||||
|
|
@ -5079,7 +5079,12 @@ impl<'a> Checker<'a> {
|
||||||
&overloaded_name,
|
&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);
|
overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,42 @@ mod tests {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn suppress_dummy_args() -> Result<()> {
|
fn suppress_dummy_args() -> Result<()> {
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
Path::new("flake8_annotations/suppress_dummy_args.py"),
|
Path::new("flake8_annotations/suppress_dummy_args.py"),
|
||||||
&Settings {
|
&Settings {
|
||||||
flake8_annotations: super::settings::Settings {
|
flake8_annotations: super::settings::Settings {
|
||||||
mypy_init_return: false,
|
|
||||||
suppress_dummy_args: true,
|
suppress_dummy_args: true,
|
||||||
suppress_none_returning: false,
|
..Default::default()
|
||||||
allow_star_arg_any: false,
|
|
||||||
},
|
},
|
||||||
..Settings::for_rules(vec![
|
..Settings::for_rules(vec![
|
||||||
Rule::MissingTypeFunctionArgument,
|
Rule::MissingTypeFunctionArgument,
|
||||||
|
|
@ -70,9 +96,7 @@ mod tests {
|
||||||
&Settings {
|
&Settings {
|
||||||
flake8_annotations: super::settings::Settings {
|
flake8_annotations: super::settings::Settings {
|
||||||
mypy_init_return: true,
|
mypy_init_return: true,
|
||||||
suppress_dummy_args: false,
|
..Default::default()
|
||||||
suppress_none_returning: false,
|
|
||||||
allow_star_arg_any: false,
|
|
||||||
},
|
},
|
||||||
..Settings::for_rules(vec![
|
..Settings::for_rules(vec![
|
||||||
Rule::MissingReturnTypePublicFunction,
|
Rule::MissingReturnTypePublicFunction,
|
||||||
|
|
@ -83,7 +107,7 @@ mod tests {
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
assert_yaml_snapshot!(diagnostics);
|
insta::assert_yaml_snapshot!(diagnostics);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,17 +117,21 @@ mod tests {
|
||||||
Path::new("flake8_annotations/suppress_none_returning.py"),
|
Path::new("flake8_annotations/suppress_none_returning.py"),
|
||||||
&Settings {
|
&Settings {
|
||||||
flake8_annotations: super::settings::Settings {
|
flake8_annotations: super::settings::Settings {
|
||||||
mypy_init_return: false,
|
|
||||||
suppress_dummy_args: false,
|
|
||||||
suppress_none_returning: true,
|
suppress_none_returning: true,
|
||||||
allow_star_arg_any: false,
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Settings::for_rules(vec![
|
..Settings::for_rules(vec![
|
||||||
|
Rule::MissingTypeFunctionArgument,
|
||||||
|
Rule::MissingTypeArgs,
|
||||||
|
Rule::MissingTypeKwargs,
|
||||||
|
Rule::MissingTypeSelf,
|
||||||
|
Rule::MissingTypeCls,
|
||||||
Rule::MissingReturnTypePublicFunction,
|
Rule::MissingReturnTypePublicFunction,
|
||||||
Rule::MissingReturnTypePrivateFunction,
|
Rule::MissingReturnTypePrivateFunction,
|
||||||
Rule::MissingReturnTypeSpecialMethod,
|
Rule::MissingReturnTypeSpecialMethod,
|
||||||
Rule::MissingReturnTypeStaticMethod,
|
Rule::MissingReturnTypeStaticMethod,
|
||||||
Rule::MissingReturnTypeClassMethod,
|
Rule::MissingReturnTypeClassMethod,
|
||||||
|
Rule::DynamicallyTypedExpression,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -117,10 +145,8 @@ mod tests {
|
||||||
Path::new("flake8_annotations/allow_star_arg_any.py"),
|
Path::new("flake8_annotations/allow_star_arg_any.py"),
|
||||||
&Settings {
|
&Settings {
|
||||||
flake8_annotations: super::settings::Settings {
|
flake8_annotations: super::settings::Settings {
|
||||||
mypy_init_return: false,
|
|
||||||
suppress_dummy_args: false,
|
|
||||||
suppress_none_returning: false,
|
|
||||||
allow_star_arg_any: true,
|
allow_star_arg_any: true,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Settings::for_rules(vec![Rule::DynamicallyTypedExpression])
|
..Settings::for_rules(vec![Rule::DynamicallyTypedExpression])
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -185,12 +185,16 @@ fn is_none_returning(body: &[Stmt]) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ANN401
|
/// ANN401
|
||||||
fn check_dynamically_typed<F>(checker: &mut Checker, annotation: &Expr, func: F)
|
fn check_dynamically_typed<F>(
|
||||||
where
|
checker: &Checker,
|
||||||
|
annotation: &Expr,
|
||||||
|
func: F,
|
||||||
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
) where
|
||||||
F: FnOnce() -> String,
|
F: FnOnce() -> String,
|
||||||
{
|
{
|
||||||
if checker.match_typing_expr(annotation, "Any") {
|
if checker.match_typing_expr(annotation, "Any") {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
DynamicallyTypedExpression { name: func() },
|
DynamicallyTypedExpression { name: func() },
|
||||||
Range::from_located(annotation),
|
Range::from_located(annotation),
|
||||||
));
|
));
|
||||||
|
|
@ -198,273 +202,301 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate flake8-annotation checks for a given `Definition`.
|
/// 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<Diagnostic> {
|
||||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||||
// We could adhere more closely to `flake8-annotations` by defining public
|
// We could adhere more closely to `flake8-annotations` by defining public
|
||||||
// vs. secret vs. protected.
|
// vs. secret vs. protected.
|
||||||
match &definition.kind {
|
if let DefinitionKind::Function(stmt)
|
||||||
DefinitionKind::Module => {}
|
| DefinitionKind::NestedFunction(stmt)
|
||||||
DefinitionKind::Package => {}
|
| DefinitionKind::Method(stmt) = &definition.kind
|
||||||
DefinitionKind::Class(_) => {}
|
{
|
||||||
DefinitionKind::NestedClass(_) => {}
|
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
|
||||||
DefinitionKind::Function(stmt)
|
let (name, args, returns, body) = match_function_def(stmt);
|
||||||
| DefinitionKind::NestedFunction(stmt)
|
// Keep track of whether we've seen any typed arguments or return values.
|
||||||
| DefinitionKind::Method(stmt) => {
|
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||||
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
|
let mut has_typed_return = false; // Return value has been typed?
|
||||||
let (name, args, returns, body) = match_function_def(stmt);
|
let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument?
|
||||||
let mut has_any_typed_arg = false;
|
|
||||||
|
|
||||||
// ANN001, ANN401
|
// Temporary storage for diagnostics; we emit them at the end
|
||||||
for arg in args
|
// unless configured to suppress ANN* for declarations that are fully untyped.
|
||||||
.args
|
let mut diagnostics = Vec::new();
|
||||||
.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),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANN002, ANN401
|
// ANN001, ANN401
|
||||||
if let Some(arg) = &args.vararg {
|
for arg in args
|
||||||
if let Some(expr) = &arg.node.annotation {
|
.args
|
||||||
has_any_typed_arg = true;
|
.iter()
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
.chain(args.posonlyargs.iter())
|
||||||
if checker
|
.chain(args.kwonlyargs.iter())
|
||||||
.settings
|
.skip(
|
||||||
.rules
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
.enabled(&Rule::DynamicallyTypedExpression)
|
usize::from(
|
||||||
{
|
is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)),
|
||||||
let name = &arg.node.arg;
|
),
|
||||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
)
|
||||||
}
|
{
|
||||||
}
|
// ANN401 for dynamically typed arguments
|
||||||
} else {
|
if let Some(annotation) = &arg.node.annotation {
|
||||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
has_any_typed_arg = true;
|
||||||
&& 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 {
|
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::DynamicallyTypedExpression)
|
.enabled(&Rule::DynamicallyTypedExpression)
|
||||||
{
|
{
|
||||||
check_dynamically_typed(checker, expr, || name.to_string());
|
check_dynamically_typed(
|
||||||
|
checker,
|
||||||
|
annotation,
|
||||||
|
|| arg.node.arg.to_string(),
|
||||||
|
&mut diagnostics,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Allow omission of return annotation if the function only returns `None`
|
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||||
// (explicitly or implicitly).
|
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||||
if checker.settings.flake8_annotations.suppress_none_returning
|
|
||||||
&& is_none_returning(body)
|
|
||||||
{
|
{
|
||||||
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
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::MissingReturnTypeClassMethod)
|
.enabled(&Rule::DynamicallyTypedExpression)
|
||||||
{
|
{
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
let name = &arg.node.arg;
|
||||||
MissingReturnTypeClassMethod {
|
check_dynamically_typed(
|
||||||
name: name.to_string(),
|
checker,
|
||||||
},
|
expr,
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
|| 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
|
if checker.settings.rules.enabled(&Rule::MissingTypeArgs) {
|
||||||
.settings
|
diagnostics.push(Diagnostic::new(
|
||||||
.rules
|
MissingTypeArgs {
|
||||||
.enabled(&Rule::MissingReturnTypeStaticMethod)
|
name: arg.node.arg.to_string(),
|
||||||
{
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
MissingReturnTypeStaticMethod {
|
|
||||||
name: name.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
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::MissingReturnTypeSpecialMethod)
|
.enabled(&Rule::DynamicallyTypedExpression)
|
||||||
{
|
{
|
||||||
if !(checker.settings.flake8_annotations.mypy_init_return
|
let name = &arg.node.arg;
|
||||||
&& has_any_typed_arg)
|
check_dynamically_typed(
|
||||||
{
|
checker,
|
||||||
let mut diagnostic = Diagnostic::new(
|
expr,
|
||||||
MissingReturnTypeSpecialMethod {
|
|| format!("**{name}"),
|
||||||
name: name.to_string(),
|
&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),
|
Range::from_located(arg),
|
||||||
);
|
));
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
}
|
||||||
match fixes::add_return_none_annotation(checker.locator, stmt) {
|
} else {
|
||||||
Ok(fix) => {
|
if checker.settings.rules.enabled(&Rule::MissingTypeSelf) {
|
||||||
diagnostic.amend(fix);
|
diagnostics.push(Diagnostic::new(
|
||||||
}
|
MissingTypeSelf {
|
||||||
Err(e) => error!("Failed to generate fix: {e}"),
|
name: arg.node.arg.to_string(),
|
||||||
}
|
},
|
||||||
}
|
Range::from_located(arg),
|
||||||
checker.diagnostics.push(diagnostic);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_method && visibility::is_magic(cast::name(stmt)) {
|
} else {
|
||||||
if checker
|
has_typed_self_or_cls = true;
|
||||||
.settings
|
}
|
||||||
.rules
|
}
|
||||||
.enabled(&Rule::MissingReturnTypeSpecialMethod)
|
}
|
||||||
|
|
||||||
|
// 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 {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
));
|
);
|
||||||
}
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
} else {
|
match fixes::add_return_none_annotation(checker.locator, stmt) {
|
||||||
match visibility {
|
Ok(fix) => {
|
||||||
Visibility::Public => {
|
diagnostic.amend(fix);
|
||||||
if checker
|
}
|
||||||
.settings
|
Err(e) => error!("Failed to generate fix: {e}"),
|
||||||
.rules
|
|
||||||
.enabled(&Rule::MissingReturnTypePublicFunction)
|
|
||||||
{
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
MissingReturnTypePublicFunction {
|
|
||||||
name: name.to_string(),
|
|
||||||
},
|
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Visibility::Private => {
|
diagnostics.push(diagnostic);
|
||||||
if checker
|
}
|
||||||
.settings
|
}
|
||||||
.rules
|
} else if is_method && visibility::is_magic(cast::name(stmt)) {
|
||||||
.enabled(&Rule::MissingReturnTypePrivateFunction)
|
if checker
|
||||||
{
|
.settings
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
.rules
|
||||||
MissingReturnTypePrivateFunction {
|
.enabled(&Rule::MissingReturnTypeSpecialMethod)
|
||||||
name: name.to_string(),
|
{
|
||||||
},
|
diagnostics.push(Diagnostic::new(
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
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![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,15 @@ pub struct Options {
|
||||||
/// Whether to suppress `ANN401` for dynamically typed `*args` and
|
/// Whether to suppress `ANN401` for dynamically typed `*args` and
|
||||||
/// `**kwargs` arguments.
|
/// `**kwargs` arguments.
|
||||||
pub allow_star_arg_any: Option<bool>,
|
pub allow_star_arg_any: Option<bool>,
|
||||||
|
#[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<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Hash)]
|
#[derive(Debug, Default, Hash)]
|
||||||
|
|
@ -58,6 +67,7 @@ pub struct Settings {
|
||||||
pub suppress_dummy_args: bool,
|
pub suppress_dummy_args: bool,
|
||||||
pub suppress_none_returning: bool,
|
pub suppress_none_returning: bool,
|
||||||
pub allow_star_arg_any: bool,
|
pub allow_star_arg_any: bool,
|
||||||
|
pub ignore_fully_untyped: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Options> for Settings {
|
impl From<Options> for Settings {
|
||||||
|
|
@ -67,6 +77,7 @@ impl From<Options> for Settings {
|
||||||
suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false),
|
suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false),
|
||||||
suppress_none_returning: options.suppress_none_returning.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),
|
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<Settings> for Options {
|
||||||
suppress_dummy_args: Some(settings.suppress_dummy_args),
|
suppress_dummy_args: Some(settings.suppress_dummy_args),
|
||||||
suppress_none_returning: Some(settings.suppress_none_returning),
|
suppress_none_returning: Some(settings.suppress_none_returning),
|
||||||
allow_star_arg_any: Some(settings.allow_star_arg_any),
|
allow_star_arg_any: Some(settings.allow_star_arg_any),
|
||||||
|
ignore_fully_untyped: Some(settings.ignore_fully_untyped),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: ~
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/rules/flake8_annotations/mod.rs
|
source: crates/ruff/src/rules/flake8_annotations/mod.rs
|
||||||
expression: diagnostics
|
expression: diagnostics
|
||||||
---
|
---
|
||||||
- kind:
|
- kind:
|
||||||
|
|
@ -12,8 +12,7 @@ expression: diagnostics
|
||||||
row: 5
|
row: 5
|
||||||
column: 16
|
column: 16
|
||||||
fix:
|
fix:
|
||||||
content:
|
content: " -> None"
|
||||||
- " -> None"
|
|
||||||
location:
|
location:
|
||||||
row: 5
|
row: 5
|
||||||
column: 22
|
column: 22
|
||||||
|
|
@ -31,8 +30,7 @@ expression: diagnostics
|
||||||
row: 11
|
row: 11
|
||||||
column: 16
|
column: 16
|
||||||
fix:
|
fix:
|
||||||
content:
|
content: " -> None"
|
||||||
- " -> None"
|
|
||||||
location:
|
location:
|
||||||
row: 11
|
row: 11
|
||||||
column: 27
|
column: 27
|
||||||
|
|
@ -61,8 +59,7 @@ expression: diagnostics
|
||||||
row: 47
|
row: 47
|
||||||
column: 16
|
column: 16
|
||||||
fix:
|
fix:
|
||||||
content:
|
content: " -> None"
|
||||||
- " -> None"
|
|
||||||
location:
|
location:
|
||||||
row: 47
|
row: 47
|
||||||
column: 28
|
column: 28
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/rules/flake8_annotations/mod.rs
|
source: crates/ruff/src/rules/flake8_annotations/mod.rs
|
||||||
expression: diagnostics
|
expression: diagnostics
|
||||||
---
|
---
|
||||||
- kind:
|
- kind:
|
||||||
|
|
@ -24,4 +24,15 @@ expression: diagnostics
|
||||||
column: 7
|
column: 7
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MissingTypeFunctionArgument:
|
||||||
|
name: a
|
||||||
|
location:
|
||||||
|
row: 59
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 59
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -541,6 +541,13 @@
|
||||||
"null"
|
"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": {
|
"mypy-init-return": {
|
||||||
"description": "Whether to allow the omission of a return type hint for `__init__` if at least one argument is annotated.",
|
"description": "Whether to allow the omission of a return type hint for `__init__` if at least one argument is annotated.",
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue