Skip to content

Commit

Permalink
Add ConvertExitToSysExit
Browse files Browse the repository at this point in the history
--fix does not import sys if needed
exit inside functions is not detected
  • Loading branch information
JonathanPlasse committed Nov 19, 2022
1 parent 64e6e44 commit e3cbc0c
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) 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'?) | |
| RUF101 | ConvertExitToSysExit | `exit()` is only available in the interpreter, should use `sys.exit()` instead | |
### Meta rules
Expand Down
26 changes: 26 additions & 0 deletions resources/test/fixtures/RUF101.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
exit(1)


import sys

exit(0)


def main():
exit(3) # Is not detected


sys.exit(0)
del sys


import sys as sys2

exit(2)


def exit(e):
pass


exit(0)
7 changes: 6 additions & 1 deletion src/check_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print,
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade,
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, rules,
};

const GLOBAL_SCOPE_INDEX: usize = 0;
Expand Down Expand Up @@ -1477,6 +1477,11 @@ where
}
}
}

// Ruff
if self.settings.enabled.contains(&CheckCode::RUF101) {
rules::plugins::convert_sys_to_sys_exit(self, func);
}
}
ExprKind::Dict { keys, .. } => {
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
Expand Down
8 changes: 8 additions & 0 deletions src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ pub enum CheckCode {
RUF001,
RUF002,
RUF003,
RUF101,
// Meta
M001,
}
Expand Down Expand Up @@ -581,6 +582,7 @@ pub enum CheckKind {
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
ConvertExitToSysExit,
// Meta
UnusedNOQA(Option<Vec<String>>),
}
Expand Down Expand Up @@ -869,6 +871,7 @@ impl CheckCode {
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
CheckCode::RUF101 => CheckKind::ConvertExitToSysExit,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
Expand Down Expand Up @@ -1078,6 +1081,7 @@ impl CheckCode {
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
}
}
Expand Down Expand Up @@ -1308,6 +1312,7 @@ impl CheckKind {
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
CheckKind::ConvertExitToSysExit => &CheckCode::RUF101,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
Expand Down Expand Up @@ -1994,6 +1999,9 @@ impl CheckKind {
'{representant}'?)"
)
}
CheckKind::ConvertExitToSysExit => "`exit()` is only available in the interpreter, \
should use `sys.exit()` instead"
.to_string(),
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
Expand Down
16 changes: 15 additions & 1 deletion src/checks_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ pub enum CheckCodePrefix {
RUF001,
RUF002,
RUF003,
RUF1,
RUF10,
RUF101,
S,
S1,
S10,
Expand Down Expand Up @@ -1057,12 +1060,20 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::RUF => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF101,
],
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::RUF1 => vec![CheckCode::RUF101],
CheckCodePrefix::RUF10 => vec![CheckCode::RUF101],
CheckCodePrefix::RUF101 => vec![CheckCode::RUF101],
CheckCodePrefix::S => vec![
CheckCode::S101,
CheckCode::S102,
Expand Down Expand Up @@ -1466,6 +1477,9 @@ impl CheckCodePrefix {
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF003 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::RUF10 => PrefixSpecificity::Tens,
CheckCodePrefix::RUF101 => PrefixSpecificity::Explicit,
CheckCodePrefix::S => PrefixSpecificity::Category,
CheckCodePrefix::S1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::S10 => PrefixSpecificity::Tens,
Expand Down
1 change: 1 addition & 0 deletions src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ 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::RUF101, Path::new("RUF101.py"); "RUF101")]
#[test_case(CheckCode::YTT101, Path::new("YTT101.py"); "YTT101")]
#[test_case(CheckCode::YTT102, Path::new("YTT102.py"); "YTT102")]
#[test_case(CheckCode::YTT103, Path::new("YTT103.py"); "YTT103")]
Expand Down
1 change: 1 addition & 0 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Module for Ruff-specific rules.
pub mod checks;
pub mod plugins;
44 changes: 44 additions & 0 deletions src/rules/plugins/convert_sys_to_sys_exit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use rustpython_ast::{Expr, ExprKind};

use crate::ast::types::{Binding, BindingKind, Range, Scope};
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};

fn get_sys_import(scope: &Scope) -> Option<String> {
let sys_binding_kind =
scope.values.values().map(|v| &v.kind).find(
|b| matches!(b, BindingKind::Importation(_, sys, _) if sys == &"sys".to_string()),
);
if let Some(BindingKind::Importation(name, ..)) = sys_binding_kind {
return Some(name.clone());
}
None
}

pub fn convert_sys_to_sys_exit(checker: &mut Checker, func: &Expr) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "exit" {
let scope = checker.current_scope();
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("exit")
{
let mut check =
Check::new(CheckKind::ConvertExitToSysExit, Range::from_located(func));
if checker.patch(check.kind.code()) {
if let Some(mut content) = get_sys_import(scope) {
content.push_str(".exit");
check.amend(Fix::replacement(
content,
func.location,
func.end_location.unwrap(),
))
}
}
checker.add_check(check);
}
}
}
}
3 changes: 3 additions & 0 deletions src/rules/plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub use convert_sys_to_sys_exit::convert_sys_to_sys_exit;

mod convert_sys_to_sys_exit;
47 changes: 47 additions & 0 deletions src/snapshots/ruff__linter__tests__RUF101_RUF101.py.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
source: src/linter.rs
expression: checks
---
- kind: ConvertExitToSysExit
location:
row: 1
column: 0
end_location:
row: 1
column: 4
fix: ~
- kind: ConvertExitToSysExit
location:
row: 6
column: 0
end_location:
row: 6
column: 4
fix:
patch:
content: sys.exit
location:
row: 6
column: 0
end_location:
row: 6
column: 4
applied: false
- kind: ConvertExitToSysExit
location:
row: 19
column: 0
end_location:
row: 19
column: 4
fix:
patch:
content: sys2.exit
location:
row: 19
column: 0
end_location:
row: 19
column: 4
applied: false

0 comments on commit e3cbc0c

Please sign in to comment.