ruff/src/flake8_annotations/plugins.rs

292 lines
11 KiB
Rust

use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::visibility::Visibility;
use crate::{visibility, Check};
#[derive(Default)]
struct ReturnStatementVisitor<'a> {
returns: Vec<&'a Option<Box<Expr>>>,
}
impl<'a, 'b> Visitor<'b> for ReturnStatementVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => self.returns.push(value),
_ => visitor::walk_stmt(self, stmt),
}
}
}
fn is_none_returning(body: &[Stmt]) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
for expr in visitor.returns.into_iter().flatten() {
if !matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
return false;
}
}
true
}
fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef {
name,
args,
returns,
body,
..
}
| StmtKind::AsyncFunctionDef {
name,
args,
returns,
body,
..
} => (name, args, returns, body),
_ => panic!("Found non-FunctionDef in match_name"),
}
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
// 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) => {
let (name, args, returns, body) = match_function_def(stmt);
// ANN001
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
{
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
// ANN002
if let Some(arg) = &args.vararg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
// ANN003
if let Some(arg) = &args.kwarg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
// ANN201, ANN202
if returns.is_none() {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
{
return;
}
match visibility {
Visibility::Public => {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
Visibility::Private => {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
}
}
DefinitionKind::Method(stmt) => {
let (name, args, returns, body) = match_function_def(stmt);
let mut has_any_typed_arg = false;
// ANN001
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(!visibility::is_staticmethod(stmt)),
)
{
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
} else {
has_any_typed_arg = true;
}
}
// ANN002
if let Some(arg) = &args.vararg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
} else {
has_any_typed_arg = true;
}
}
// ANN003
if let Some(arg) = &args.kwarg {
if arg.node.annotation.is_none() {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
} else {
has_any_typed_arg = true;
}
}
// ANN101, ANN102
if !visibility::is_staticmethod(stmt) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
Range::from_located(arg),
));
} else {
checker.add_check(Check::new(
CheckKind::MissingTypeSelf(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN201, ANN202
if returns.is_none() {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
{
return;
}
if visibility::is_classmethod(stmt) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
));
} else if visibility::is_staticmethod(stmt) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
Range::from_located(stmt),
));
} else if visibility::is_magic(stmt) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
} else if visibility::is_init(stmt) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg)
{
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else {
match visibility {
Visibility::Public => {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
Visibility::Private => {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
}
}
}
}
}