Extend and rename RUF004 to PLR1722 (#1036)

This commit is contained in:
Jonathan Plasse 2022-12-04 16:20:12 +01:00 committed by GitHub
parent 1d61db5b62
commit 8f6b2fb32b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 614 additions and 376 deletions

View file

@ -773,6 +773,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
| PLR0402 | ConsiderUsingFromImport | Consider using `from ... import ...` | |
| PLR1701 | ConsiderMergingIsinstance | Consider merging these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 |
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
### Ruff-specific rules
@ -782,7 +783,6 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
| RUF004 | ConvertExitToSysExit | `exit()` is only available in the interpreter, use `sys.exit()` instead | 🛠 |
| RUF100 | UnusedNOQA | Unused `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->

View file

@ -1,5 +1,7 @@
exit(0)
quit(0)
def main():
exit(2)
quit(2)

View file

@ -1,10 +1,12 @@
import sys
exit(0)
quit(0)
def main():
exit(1)
quit(1)
sys.exit(2)

View file

@ -1,7 +1,9 @@
import sys as sys2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View file

@ -1,7 +1,9 @@
from sys import exit
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View file

@ -1,7 +1,9 @@
from sys import exit as exit2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View file

@ -1,7 +1,9 @@
from sys import *
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View file

@ -1,12 +1,19 @@
exit(0)
quit(0)
def exit(e):
pass
def quit(e):
pass
exit(1)
quit(1)
def main():
exit(2)
quit(2)

View file

@ -37,7 +37,7 @@ use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, rules,
pyflakes, pygrep_hooks, pylint, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@ -1757,10 +1757,8 @@ where
if self.settings.enabled.contains(&CheckCode::PLC3002) {
pylint::plugins::unnecessary_direct_lambda_call(self, expr, func);
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF004) {
rules::plugins::convert_exit_to_sys_exit(self, func);
if self.settings.enabled.contains(&CheckCode::PLR1722) {
pylint::plugins::consider_using_sys_exit(self, func);
}
}
ExprKind::Dict { keys, .. } => {

View file

@ -100,6 +100,7 @@ pub enum CheckCode {
PLR0206,
PLR0402,
PLR1701,
PLR1722,
PLW0120,
// flake8-builtins
A001,
@ -293,7 +294,6 @@ pub enum CheckCode {
RUF001,
RUF002,
RUF003,
RUF004,
RUF100,
// pygrep-hooks
PGH001,
@ -583,6 +583,7 @@ pub enum CheckKind {
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
UselessElseOnLoop,
ConsiderUsingSysExit,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
@ -777,7 +778,6 @@ pub enum CheckKind {
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
ConvertExitToSysExit,
UnusedNOQA(Option<Vec<String>>),
}
@ -886,6 +886,7 @@ impl CheckCode {
CheckCode::PLR1701 => {
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
}
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
@ -1116,7 +1117,6 @@ impl CheckCode {
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
CheckCode::RUF004 => CheckKind::ConvertExitToSysExit,
CheckCode::RUF100 => CheckKind::UnusedNOQA(None),
}
}
@ -1312,6 +1312,7 @@ impl CheckCode {
CheckCode::PLR0206 => CheckCategory::Pylint,
CheckCode::PLR0402 => CheckCategory::Pylint,
CheckCode::PLR1701 => CheckCategory::Pylint,
CheckCode::PLR1722 => CheckCategory::Pylint,
CheckCode::PLW0120 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
@ -1328,7 +1329,6 @@ impl CheckCode {
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF004 => CheckCategory::Ruff,
CheckCode::RUF100 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
@ -1441,6 +1441,7 @@ impl CheckKind {
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
@ -1636,7 +1637,6 @@ impl CheckKind {
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
CheckKind::ConvertExitToSysExit => &CheckCode::RUF004,
CheckKind::UnusedNOQA(_) => &CheckCode::RUF100,
}
}
@ -1847,6 +1847,7 @@ impl CheckKind {
CheckKind::UselessElseOnLoop => "Else clause on loop without a break statement, \
remove the else and de-indent all the code inside it"
.to_string(),
CheckKind::ConsiderUsingSysExit => "Consider using `sys.exit()`".to_string(),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@ -2434,9 +2435,6 @@ impl CheckKind {
'{representant}'?)"
)
}
CheckKind::ConvertExitToSysExit => "`exit()` is only available in the interpreter, \
use `sys.exit()` instead"
.to_string(),
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
Some(codes) => {
@ -2488,7 +2486,7 @@ impl CheckKind {
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConsiderUsingSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)

View file

@ -309,6 +309,8 @@ pub enum CheckCodePrefix {
PLR17,
PLR170,
PLR1701,
PLR172,
PLR1722,
PLW,
PLW0,
PLW01,
@ -338,7 +340,6 @@ pub enum CheckCodePrefix {
RUF001,
RUF002,
RUF003,
RUF004,
RUF1,
RUF10,
RUF100,
@ -1229,9 +1230,12 @@ impl CheckCodePrefix {
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE1142 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLR => {
vec![CheckCode::PLR0206, CheckCode::PLR0402, CheckCode::PLR1701]
}
CheckCodePrefix::PLR => vec![
CheckCode::PLR0206,
CheckCode::PLR0402,
CheckCode::PLR1701,
CheckCode::PLR1722,
],
CheckCodePrefix::PLR0 => vec![CheckCode::PLR0206, CheckCode::PLR0402],
CheckCodePrefix::PLR02 => vec![CheckCode::PLR0206],
CheckCodePrefix::PLR020 => vec![CheckCode::PLR0206],
@ -1239,10 +1243,12 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR04 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR040 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR0402 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR1 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR17 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR1 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR17 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR170 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
@ -1312,25 +1318,13 @@ impl CheckCodePrefix {
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
CheckCode::RUF100,
],
CheckCodePrefix::RUF0 => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
],
CheckCodePrefix::RUF00 => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
],
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
CheckCodePrefix::RUF004 => vec![CheckCode::RUF004],
CheckCodePrefix::RUF1 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF10 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF100 => vec![CheckCode::RUF100],
@ -1966,6 +1960,8 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR17 => SuffixLength::Two,
CheckCodePrefix::PLR170 => SuffixLength::Three,
CheckCodePrefix::PLR1701 => SuffixLength::Four,
CheckCodePrefix::PLR172 => SuffixLength::Three,
CheckCodePrefix::PLR1722 => SuffixLength::Four,
CheckCodePrefix::PLW => SuffixLength::Zero,
CheckCodePrefix::PLW0 => SuffixLength::One,
CheckCodePrefix::PLW01 => SuffixLength::Two,
@ -1995,7 +1991,6 @@ impl CheckCodePrefix {
CheckCodePrefix::RUF001 => SuffixLength::Three,
CheckCodePrefix::RUF002 => SuffixLength::Three,
CheckCodePrefix::RUF003 => SuffixLength::Three,
CheckCodePrefix::RUF004 => SuffixLength::Three,
CheckCodePrefix::RUF1 => SuffixLength::One,
CheckCodePrefix::RUF10 => SuffixLength::Two,
CheckCodePrefix::RUF100 => SuffixLength::Three,

View file

@ -18,6 +18,13 @@ mod tests {
#[test_case(CheckCode::PLR0206, Path::new("property_with_parameters.py"); "PLR0206")]
#[test_case(CheckCode::PLR0402, Path::new("import_aliasing.py"); "PLR0402")]
#[test_case(CheckCode::PLR1701, Path::new("consider_merging_isinstance.py"); "PLR1701")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_0.py"); "PLR1722_0")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_1.py"); "PLR1722_1")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_2.py"); "PLR1722_2")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_3.py"); "PLR1722_3")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_4.py"); "PLR1722_4")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_5.py"); "PLR1722_5")]
#[test_case(CheckCode::PLR1722, Path::new("consider_using_sys_exit_6.py"); "PLR1722_6")]
#[test_case(CheckCode::PLW0120, Path::new("useless_else_on_loop.py"); "PLW0120")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());

View file

@ -1,218 +0,0 @@
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{
Alias, Arguments, Boolop, Cmpop, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
};
use crate::ast::types::{FunctionScope, Range, ScopeKind};
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLC0414
pub fn useless_import_alias(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if !alias.node.name.contains('.') && &alias.node.name == asname {
let mut check = Check::new(CheckKind::UselessImportAlias, Range::from_located(alias));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
asname.to_string(),
alias.location,
alias.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
/// PLC2201
pub fn misplaced_comparison_constant(
checker: &mut Checker,
expr: &Expr,
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
) {
if let ([op], [right]) = (ops, comparators) {
if matches!(
op,
Cmpop::Eq | Cmpop::NotEq | Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE,
) && matches!(&left.node, &ExprKind::Constant { .. })
&& !matches!(&right.node, &ExprKind::Constant { .. })
{
let reversed_op = match op {
Cmpop::Eq => "==",
Cmpop::NotEq => "!=",
Cmpop::Lt => ">",
Cmpop::LtE => ">=",
Cmpop::Gt => "<",
Cmpop::GtE => "<=",
_ => unreachable!("Expected comparison operator"),
};
let suggestion = format!("{right} {reversed_op} {left}");
let mut check = Check::new(
CheckKind::MisplacedComparisonConstant(suggestion.clone()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
suggestion,
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
/// PLC3002
pub fn unnecessary_direct_lambda_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let ExprKind::Lambda { .. } = &func.node {
checker.add_check(Check::new(
CheckKind::UnnecessaryDirectLambdaCall,
Range::from_located(expr),
));
}
}
/// PLE1142
pub fn await_outside_async(checker: &mut Checker, expr: &Expr) {
if !checker
.current_scopes()
.find_map(|scope| {
if let ScopeKind::Function(FunctionScope { async_, .. }) = &scope.kind {
Some(*async_)
} else {
None
}
})
.unwrap_or(true)
{
checker.add_check(Check::new(
CheckKind::AwaitOutsideAsync,
Range::from_located(expr),
));
}
}
/// PLR0206
pub fn property_with_parameters(
checker: &mut Checker,
stmt: &Stmt,
decorator_list: &[Expr],
args: &Arguments,
) {
if decorator_list
.iter()
.any(|d| matches!(&d.node, ExprKind::Name { id, .. } if id == "property"))
{
if checker.is_builtin("property")
&& args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.count()
> 1
{
checker.add_check(Check::new(
CheckKind::PropertyWithParameters,
Range::from_located(stmt),
));
}
}
}
/// PLR0402
pub fn consider_using_from_import(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if let Some((module, name)) = alias.node.name.rsplit_once('.') {
if name == asname {
checker.add_check(Check::new(
CheckKind::ConsiderUsingFromImport(module.to_string(), name.to_string()),
Range::from_located(alias),
));
}
}
}
}
/// PLR1701
pub fn consider_merging_isinstance(
checker: &mut Checker,
expr: &Expr,
op: &Boolop,
values: &[Expr],
) {
if !matches!(op, Boolop::Or) || !checker.is_builtin("isinstance") {
return;
}
let mut obj_to_types: FxHashMap<String, FxHashSet<String>> = FxHashMap::default();
for value in values {
if let ExprKind::Call { func, args, .. } = &value.node {
if matches!(&func.node, ExprKind::Name { id, .. } if id == "isinstance") {
if let [obj, types] = &args[..] {
obj_to_types
.entry(obj.to_string())
.or_insert_with(FxHashSet::default)
.extend(match &types.node {
ExprKind::Tuple { elts, .. } => {
elts.iter().map(std::string::ToString::to_string).collect()
}
_ => {
vec![types.to_string()]
}
});
}
}
}
}
for (obj, types) in obj_to_types {
if types.len() > 1 {
checker.add_check(Check::new(
CheckKind::ConsiderMergingIsinstance(obj, types.into_iter().sorted().collect()),
Range::from_located(expr),
));
}
}
}
fn loop_exits_early(body: &[Stmt]) -> bool {
body.iter().any(|stmt| match &stmt.node {
StmtKind::If { body, .. } => loop_exits_early(body),
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
..
} => {
loop_exits_early(body)
|| handlers.iter().any(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => loop_exits_early(body),
})
|| loop_exits_early(orelse)
|| loop_exits_early(finalbody)
}
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => loop_exits_early(orelse),
StmtKind::Break { .. } => true,
_ => false,
})
}
/// PLW0120
pub fn useless_else_on_loop(checker: &mut Checker, stmt: &Stmt, body: &[Stmt], orelse: &[Stmt]) {
if !orelse.is_empty() && !loop_exits_early(body) {
checker.add_check(Check::new(
CheckKind::UselessElseOnLoop,
Range::from_located(stmt),
));
}
}

View file

@ -0,0 +1,26 @@
use rustpython_ast::Expr;
use crate::ast::types::{FunctionScope, Range, ScopeKind};
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLE1142
pub fn await_outside_async(checker: &mut Checker, expr: &Expr) {
if !checker
.current_scopes()
.find_map(|scope| {
if let ScopeKind::Function(FunctionScope { async_, .. }) = &scope.kind {
Some(*async_)
} else {
None
}
})
.unwrap_or(true)
{
checker.add_check(Check::new(
CheckKind::AwaitOutsideAsync,
Range::from_located(expr),
));
}
}

View file

@ -0,0 +1,50 @@
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Boolop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR1701
pub fn consider_merging_isinstance(
checker: &mut Checker,
expr: &Expr,
op: &Boolop,
values: &[Expr],
) {
if !matches!(op, Boolop::Or) || !checker.is_builtin("isinstance") {
return;
}
let mut obj_to_types: FxHashMap<String, FxHashSet<String>> = FxHashMap::default();
for value in values {
if let ExprKind::Call { func, args, .. } = &value.node {
if matches!(&func.node, ExprKind::Name { id, .. } if id == "isinstance") {
if let [obj, types] = &args[..] {
obj_to_types
.entry(obj.to_string())
.or_insert_with(FxHashSet::default)
.extend(match &types.node {
ExprKind::Tuple { elts, .. } => {
elts.iter().map(std::string::ToString::to_string).collect()
}
_ => {
vec![types.to_string()]
}
});
}
}
}
}
for (obj, types) in obj_to_types {
if types.len() > 1 {
checker.add_check(Check::new(
CheckKind::ConsiderMergingIsinstance(obj, types.into_iter().sorted().collect()),
Range::from_located(expr),
));
}
}
}

View file

@ -0,0 +1,20 @@
use rustpython_ast::Alias;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR0402
pub fn consider_using_from_import(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if let Some((module, name)) = alias.node.name.rsplit_once('.') {
if name == asname {
checker.add_check(Check::new(
CheckKind::ConsiderUsingFromImport(module.to_string(), name.to_string()),
Range::from_located(alias),
));
}
}
}
}

View file

@ -19,11 +19,6 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
})
}
/// Return `true` if `exit` is (still) bound as a built-in in the current scope.
fn has_builtin_exit_in_scope(checker: &Checker) -> bool {
!is_module_star_imported(checker, "sys") && checker.is_builtin("exit")
}
/// Return the appropriate `sys.exit` reference based on the current set of
/// imports, or `None` is `sys.exit` hasn't been imported.
fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option<String> {
@ -65,22 +60,27 @@ fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -
}
/// RUF004
pub fn convert_exit_to_sys_exit(checker: &mut Checker, func: &Expr) {
pub fn consider_using_sys_exit(checker: &mut Checker, func: &Expr) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "exit" {
if has_builtin_exit_in_scope(checker) {
let mut check =
Check::new(CheckKind::ConvertExitToSysExit, Range::from_located(func));
if checker.patch(check.kind.code()) {
if let Some(content) = get_member_import_name_alias(checker, "sys", "exit") {
check.amend(Fix::replacement(
content,
func.location,
func.end_location.unwrap(),
));
for name in ["exit", "quit"] {
if id == name {
if !(name == "exit" && is_module_star_imported(checker, "sys"))
&& checker.is_builtin(name)
{
let mut check =
Check::new(CheckKind::ConsiderUsingSysExit, Range::from_located(func));
if checker.patch(check.kind.code()) {
if let Some(content) = get_member_import_name_alias(checker, "sys", "exit")
{
check.amend(Fix::replacement(
content,
func.location,
func.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
checker.add_check(check);
}
}
}

View file

@ -0,0 +1,48 @@
use rustpython_ast::{Cmpop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLC2201
pub fn misplaced_comparison_constant(
checker: &mut Checker,
expr: &Expr,
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
) {
if let ([op], [right]) = (ops, comparators) {
if matches!(
op,
Cmpop::Eq | Cmpop::NotEq | Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE,
) && matches!(&left.node, &ExprKind::Constant { .. })
&& !matches!(&right.node, &ExprKind::Constant { .. })
{
let reversed_op = match op {
Cmpop::Eq => "==",
Cmpop::NotEq => "!=",
Cmpop::Lt => ">",
Cmpop::LtE => ">=",
Cmpop::Gt => "<",
Cmpop::GtE => "<=",
_ => unreachable!("Expected comparison operator"),
};
let suggestion = format!("{right} {reversed_op} {left}");
let mut check = Check::new(
CheckKind::MisplacedComparisonConstant(suggestion.clone()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
suggestion,
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}

19
src/pylint/plugins/mod.rs Normal file
View file

@ -0,0 +1,19 @@
pub use await_outside_async::await_outside_async;
pub use consider_merging_isinstance::consider_merging_isinstance;
pub use consider_using_from_import::consider_using_from_import;
pub use consider_using_sys_exit::consider_using_sys_exit;
pub use misplaced_comparison_constant::misplaced_comparison_constant;
pub use property_with_parameters::property_with_parameters;
pub use unnecessary_direct_lambda_call::unnecessary_direct_lambda_call;
pub use useless_else_on_loop::useless_else_on_loop;
pub use useless_import_alias::useless_import_alias;
mod await_outside_async;
mod consider_merging_isinstance;
mod consider_using_from_import;
mod consider_using_sys_exit;
mod misplaced_comparison_constant;
mod property_with_parameters;
mod unnecessary_direct_lambda_call;
mod useless_else_on_loop;
mod useless_import_alias;

View file

@ -0,0 +1,34 @@
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR0206
pub fn property_with_parameters(
checker: &mut Checker,
stmt: &Stmt,
decorator_list: &[Expr],
args: &Arguments,
) {
if decorator_list
.iter()
.any(|d| matches!(&d.node, ExprKind::Name { id, .. } if id == "property"))
{
if checker.is_builtin("property")
&& args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.count()
> 1
{
checker.add_check(Check::new(
CheckKind::PropertyWithParameters,
Range::from_located(stmt),
));
}
}
}

View file

@ -0,0 +1,16 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLC3002
pub fn unnecessary_direct_lambda_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let ExprKind::Lambda { .. } = &func.node {
checker.add_check(Check::new(
CheckKind::UnnecessaryDirectLambdaCall,
Range::from_located(expr),
));
}
}

View file

@ -0,0 +1,41 @@
use rustpython_ast::{ExcepthandlerKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
fn loop_exits_early(body: &[Stmt]) -> bool {
body.iter().any(|stmt| match &stmt.node {
StmtKind::If { body, .. } => loop_exits_early(body),
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
..
} => {
loop_exits_early(body)
|| handlers.iter().any(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => loop_exits_early(body),
})
|| loop_exits_early(orelse)
|| loop_exits_early(finalbody)
}
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => loop_exits_early(orelse),
StmtKind::Break { .. } => true,
_ => false,
})
}
/// PLW0120
pub fn useless_else_on_loop(checker: &mut Checker, stmt: &Stmt, body: &[Stmt], orelse: &[Stmt]) {
if !orelse.is_empty() && !loop_exits_early(body) {
checker.add_check(Check::new(
CheckKind::UselessElseOnLoop,
Range::from_located(stmt),
));
}
}

View file

@ -0,0 +1,24 @@
use rustpython_ast::Alias;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLC0414
pub fn useless_import_alias(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if !alias.node.name.contains('.') && &alias.node.name == asname {
let mut check = Check::new(CheckKind::UselessImportAlias, Range::from_located(alias));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
asname.to_string(),
alias.location,
alias.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}

View file

@ -0,0 +1,37 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
location:
row: 1
column: 0
end_location:
row: 1
column: 4
fix: ~
- kind: ConsiderUsingSysExit
location:
row: 2
column: 0
end_location:
row: 2
column: 4
fix: ~
- kind: ConsiderUsingSysExit
location:
row: 6
column: 4
end_location:
row: 6
column: 8
fix: ~
- kind: ConsiderUsingSysExit
location:
row: 7
column: 4
end_location:
row: 7
column: 8
fix: ~

View file

@ -0,0 +1,65 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
fix:
content: sys.exit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
location:
row: 4
column: 0
end_location:
row: 4
column: 4
fix:
content: sys.exit
location:
row: 4
column: 0
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
location:
row: 8
column: 4
end_location:
row: 8
column: 8
fix:
content: sys.exit
location:
row: 8
column: 4
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix:
content: sys.exit
location:
row: 9
column: 4
end_location:
row: 9
column: 8

View file

@ -0,0 +1,65 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
fix:
content: sys2.exit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
location:
row: 4
column: 0
end_location:
row: 4
column: 4
fix:
content: sys2.exit
location:
row: 4
column: 0
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
location:
row: 8
column: 4
end_location:
row: 8
column: 8
fix:
content: sys2.exit
location:
row: 8
column: 4
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix:
content: sys2.exit
location:
row: 9
column: 4
end_location:
row: 9
column: 8

View file

@ -1,35 +1,35 @@
---
source: src/rules/mod.rs
source: src/pylint/mod.rs
expression: checks
---
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 3
row: 4
column: 0
end_location:
row: 3
row: 4
column: 4
fix:
content: exit2
content: exit
location:
row: 3
row: 4
column: 0
end_location:
row: 3
row: 4
column: 4
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 7
row: 9
column: 4
end_location:
row: 7
row: 9
column: 8
fix:
content: exit2
content: exit
location:
row: 7
row: 9
column: 4
end_location:
row: 7
row: 9
column: 8

View file

@ -0,0 +1,65 @@
---
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
fix:
content: exit2
location:
row: 3
column: 0
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
location:
row: 4
column: 0
end_location:
row: 4
column: 4
fix:
content: exit2
location:
row: 4
column: 0
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
location:
row: 8
column: 4
end_location:
row: 8
column: 8
fix:
content: exit2
location:
row: 8
column: 4
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
location:
row: 9
column: 4
end_location:
row: 9
column: 8
fix:
content: exit2
location:
row: 9
column: 4
end_location:
row: 9
column: 8

View file

@ -1,35 +1,35 @@
---
source: src/rules/mod.rs
source: src/pylint/mod.rs
expression: checks
---
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 3
row: 4
column: 0
end_location:
row: 3
row: 4
column: 4
fix:
content: sys.exit
content: exit
location:
row: 3
row: 4
column: 0
end_location:
row: 3
row: 4
column: 4
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 7
row: 9
column: 4
end_location:
row: 7
row: 9
column: 8
fix:
content: sys.exit
content: exit
location:
row: 7
row: 9
column: 4
end_location:
row: 7
row: 9
column: 8

View file

@ -1,8 +1,8 @@
---
source: src/rules/mod.rs
source: src/pylint/mod.rs
expression: checks
---
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 1
column: 0
@ -10,12 +10,12 @@ expression: checks
row: 1
column: 4
fix: ~
- kind: ConvertExitToSysExit
- kind: ConsiderUsingSysExit
location:
row: 5
column: 4
row: 2
column: 0
end_location:
row: 5
column: 8
row: 2
column: 4
fix: ~

View file

@ -1,7 +1,6 @@
//! Module for Ruff-specific rules.
pub mod checks;
pub mod plugins;
#[cfg(test)]
mod tests {
@ -18,13 +17,6 @@ mod tests {
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_0.py"); "RUF004_0")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_1.py"); "RUF004_1")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_2.py"); "RUF004_2")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_3.py"); "RUF004_3")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_4.py"); "RUF004_4")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_5.py"); "RUF004_5")]
#[test_case(CheckCode::RUF004, Path::new("RUF004_6.py"); "RUF004_6")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View file

@ -1,3 +0,0 @@
pub use convert_exit_to_sys_exit::convert_exit_to_sys_exit;
mod convert_exit_to_sys_exit;

View file

@ -1,35 +0,0 @@
---
source: src/rules/mod.rs
expression: checks
---
- kind: ConvertExitToSysExit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
fix:
content: sys2.exit
location:
row: 3
column: 0
end_location:
row: 3
column: 4
- kind: ConvertExitToSysExit
location:
row: 7
column: 4
end_location:
row: 7
column: 8
fix:
content: sys2.exit
location:
row: 7
column: 4
end_location:
row: 7
column: 8

View file

@ -1,6 +0,0 @@
---
source: src/rules/mod.rs
expression: checks
---
[]

View file

@ -1,6 +0,0 @@
---
source: src/rules/mod.rs
expression: checks
---
[]

View file

@ -1,13 +0,0 @@
---
source: src/rules/mod.rs
expression: checks
---
- kind: ConvertExitToSysExit
location:
row: 1
column: 0
end_location:
row: 1
column: 4
fix: ~