344 lines
11 KiB
Rust
344 lines
11 KiB
Rust
use itertools::Itertools;
|
|
use rustpython_ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
|
|
|
use crate::ast::types::Range;
|
|
use crate::ast::visitor::Visitor;
|
|
use crate::ast::whitespace::indentation;
|
|
use crate::autofix::Fix;
|
|
use crate::check_ast::Checker;
|
|
use crate::checks::{Branch, CheckCode, CheckKind};
|
|
use crate::flake8_return::helpers::result_exists;
|
|
use crate::flake8_return::visitor::{ReturnVisitor, Stack};
|
|
use crate::Check;
|
|
|
|
/// RET501
|
|
fn unnecessary_return_none(checker: &mut Checker, stack: &Stack) {
|
|
for (stmt, expr) in &stack.returns {
|
|
if let Some(expr) = expr {
|
|
if matches!(
|
|
expr.node,
|
|
ExprKind::Constant {
|
|
value: Constant::None,
|
|
..
|
|
}
|
|
) {
|
|
let mut check =
|
|
Check::new(CheckKind::UnnecessaryReturnNone, Range::from_located(stmt));
|
|
if checker.patch(&CheckCode::RET501) {
|
|
check.amend(Fix::replacement(
|
|
"return".to_string(),
|
|
stmt.location,
|
|
stmt.end_location.unwrap(),
|
|
));
|
|
}
|
|
checker.add_check(check);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// RET502
|
|
fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
|
|
for (stmt, expr) in &stack.returns {
|
|
if expr.is_none() {
|
|
let mut check = Check::new(CheckKind::ImplicitReturnValue, Range::from_located(stmt));
|
|
if checker.patch(&CheckCode::RET502) {
|
|
check.amend(Fix::replacement(
|
|
"return None".to_string(),
|
|
stmt.location,
|
|
stmt.end_location.unwrap(),
|
|
));
|
|
}
|
|
checker.add_check(check);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// RET503
|
|
fn implicit_return(checker: &mut Checker, last_stmt: &Stmt) {
|
|
match &last_stmt.node {
|
|
StmtKind::If { body, orelse, .. } => {
|
|
if body.is_empty() || orelse.is_empty() {
|
|
checker.add_check(Check::new(
|
|
CheckKind::ImplicitReturn,
|
|
Range::from_located(last_stmt),
|
|
));
|
|
return;
|
|
}
|
|
|
|
if let Some(last_stmt) = body.last() {
|
|
implicit_return(checker, last_stmt);
|
|
}
|
|
if let Some(last_stmt) = orelse.last() {
|
|
implicit_return(checker, last_stmt);
|
|
}
|
|
}
|
|
StmtKind::For { body, orelse, .. } | StmtKind::AsyncFor { body, orelse, .. } => {
|
|
if let Some(last_stmt) = orelse.last() {
|
|
implicit_return(checker, last_stmt);
|
|
} else if let Some(last_stmt) = body.last() {
|
|
implicit_return(checker, last_stmt);
|
|
}
|
|
}
|
|
StmtKind::With { body, .. } | StmtKind::AsyncWith { body, .. } => {
|
|
if let Some(last_stmt) = body.last() {
|
|
implicit_return(checker, last_stmt);
|
|
}
|
|
}
|
|
StmtKind::Assert { test, .. }
|
|
if matches!(
|
|
test.node,
|
|
ExprKind::Constant {
|
|
value: Constant::Bool(false),
|
|
..
|
|
}
|
|
) => {}
|
|
StmtKind::Return { .. }
|
|
| StmtKind::While { .. }
|
|
| StmtKind::Raise { .. }
|
|
| StmtKind::Try { .. } => {}
|
|
_ => {
|
|
let mut check = Check::new(CheckKind::ImplicitReturn, Range::from_located(last_stmt));
|
|
if checker.patch(&CheckCode::RET503) {
|
|
let mut content = String::new();
|
|
content.push_str(&indentation(checker, last_stmt));
|
|
content.push_str("return None");
|
|
content.push('\n');
|
|
check.amend(Fix::insertion(
|
|
content,
|
|
Location::new(last_stmt.end_location.unwrap().row() + 1, 0),
|
|
));
|
|
}
|
|
checker.add_check(check);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn has_refs_before_next_assign(id: &str, return_location: Location, stack: &Stack) -> bool {
|
|
let mut before_assign: &Location = &Location::default();
|
|
let mut after_assign: Option<&Location> = None;
|
|
if let Some(assigns) = stack.assigns.get(&id) {
|
|
for location in assigns.iter().sorted() {
|
|
if location.row() > return_location.row() {
|
|
after_assign = Some(location);
|
|
break;
|
|
}
|
|
if location.row() <= return_location.row() {
|
|
before_assign = location;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(refs) = stack.refs.get(&id) {
|
|
for location in refs {
|
|
if location.row() == return_location.row() {
|
|
continue;
|
|
}
|
|
if let Some(after_assign) = after_assign {
|
|
if before_assign.row() < location.row() && location.row() <= after_assign.row() {
|
|
return true;
|
|
}
|
|
} else if before_assign.row() < location.row() {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn has_refs_or_assigns_within_try_or_loop(id: &str, stack: &Stack) -> bool {
|
|
if let Some(refs) = stack.refs.get(&id) {
|
|
for location in refs {
|
|
for (try_location, try_end_location) in &stack.tries {
|
|
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
|
|
return true;
|
|
}
|
|
}
|
|
for (loop_location, loop_end_location) in &stack.loops {
|
|
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(refs) = stack.assigns.get(&id) {
|
|
for location in refs {
|
|
for (try_location, try_end_location) in &stack.tries {
|
|
if try_location.row() < location.row() && location.row() <= try_end_location.row() {
|
|
return true;
|
|
}
|
|
}
|
|
for (loop_location, loop_end_location) in &stack.loops {
|
|
if loop_location.row() < location.row() && location.row() <= loop_end_location.row()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// RET504
|
|
fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
|
|
if let ExprKind::Name { id, .. } = &expr.node {
|
|
if !stack.assigns.contains_key(id.as_str()) {
|
|
return;
|
|
}
|
|
|
|
if !stack.refs.contains_key(id.as_str()) {
|
|
checker.add_check(Check::new(
|
|
CheckKind::UnnecessaryAssign,
|
|
Range::from_located(expr),
|
|
));
|
|
return;
|
|
}
|
|
|
|
if has_refs_before_next_assign(id, expr.location, stack)
|
|
|| has_refs_or_assigns_within_try_or_loop(id, stack)
|
|
{
|
|
return;
|
|
}
|
|
|
|
checker.add_check(Check::new(
|
|
CheckKind::UnnecessaryAssign,
|
|
Range::from_located(expr),
|
|
));
|
|
}
|
|
}
|
|
|
|
/// RET505, RET506, RET507, RET508
|
|
fn superfluous_else_node(checker: &mut Checker, stmt: &Stmt, branch: Branch) -> bool {
|
|
if let StmtKind::If { body, .. } = &stmt.node {
|
|
for child in body {
|
|
if matches!(child.node, StmtKind::Return { .. }) {
|
|
if checker.settings.enabled.contains(&CheckCode::RET505) {
|
|
checker.add_check(Check::new(
|
|
CheckKind::SuperfluousElseReturn(branch),
|
|
Range::from_located(stmt),
|
|
));
|
|
}
|
|
return true;
|
|
}
|
|
if matches!(child.node, StmtKind::Break) {
|
|
if checker.settings.enabled.contains(&CheckCode::RET508) {
|
|
checker.add_check(Check::new(
|
|
CheckKind::SuperfluousElseBreak(branch),
|
|
Range::from_located(stmt),
|
|
));
|
|
}
|
|
return true;
|
|
}
|
|
if matches!(child.node, StmtKind::Raise { .. }) {
|
|
if checker.settings.enabled.contains(&CheckCode::RET506) {
|
|
checker.add_check(Check::new(
|
|
CheckKind::SuperfluousElseRaise(branch),
|
|
Range::from_located(stmt),
|
|
));
|
|
}
|
|
return true;
|
|
}
|
|
if matches!(child.node, StmtKind::Continue) {
|
|
if checker.settings.enabled.contains(&CheckCode::RET507) {
|
|
checker.add_check(Check::new(
|
|
CheckKind::SuperfluousElseContinue(branch),
|
|
Range::from_located(stmt),
|
|
));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// RET505, RET506, RET507, RET508
|
|
fn superfluous_elif(checker: &mut Checker, stack: &Stack) -> bool {
|
|
for stmt in &stack.elifs {
|
|
if superfluous_else_node(checker, stmt, Branch::Elif) {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// RET505, RET506, RET507, RET508
|
|
fn superfluous_else(checker: &mut Checker, stack: &Stack) -> bool {
|
|
for stmt in &stack.ifs {
|
|
if let StmtKind::If { orelse, .. } = &stmt.node {
|
|
if !orelse.is_empty() {
|
|
if superfluous_else_node(checker, stmt, Branch::Else) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Run all checks from the `flake8-return` plugin.
|
|
pub fn function(checker: &mut Checker, body: &[Stmt]) {
|
|
// Skip empty functions.
|
|
if body.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// Find the last statement in the function.
|
|
let last_stmt = body.last().unwrap();
|
|
|
|
// Skip functions that consist of a single return statement.
|
|
if body.len() == 1 && matches!(last_stmt.node, StmtKind::Return { .. }) {
|
|
return;
|
|
}
|
|
|
|
// Traverse the function body, to collect the stack.
|
|
let stack = {
|
|
let mut visitor = ReturnVisitor::default();
|
|
for stmt in body {
|
|
visitor.visit_stmt(stmt);
|
|
}
|
|
visitor.stack
|
|
};
|
|
|
|
if checker.settings.enabled.contains(&CheckCode::RET505)
|
|
|| checker.settings.enabled.contains(&CheckCode::RET506)
|
|
|| checker.settings.enabled.contains(&CheckCode::RET507)
|
|
|| checker.settings.enabled.contains(&CheckCode::RET508)
|
|
{
|
|
if superfluous_elif(checker, &stack) {
|
|
return;
|
|
}
|
|
if superfluous_else(checker, &stack) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Skip any functions without return statements.
|
|
if stack.returns.is_empty() {
|
|
return;
|
|
}
|
|
|
|
if !result_exists(&stack.returns) {
|
|
if checker.settings.enabled.contains(&CheckCode::RET501) {
|
|
unnecessary_return_none(checker, &stack);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if checker.settings.enabled.contains(&CheckCode::RET502) {
|
|
implicit_return_value(checker, &stack);
|
|
}
|
|
if checker.settings.enabled.contains(&CheckCode::RET503) {
|
|
implicit_return(checker, last_stmt);
|
|
}
|
|
|
|
if checker.settings.enabled.contains(&CheckCode::RET504) {
|
|
for (_, expr) in &stack.returns {
|
|
if let Some(expr) = expr {
|
|
unnecessary_assign(checker, &stack, expr);
|
|
}
|
|
}
|
|
}
|
|
}
|