mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[pylint
] Convert a code
keyword argument to a positional argument (PLR1722
) (#16424)
The PR addresses issue #16396 . Specifically: - If the exit statement contains a code keyword argument, it is converted into a positional argument. - If retrieving the code from the exit statement is not possible, a violation is raised without suggesting a fix. --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
parent
c4578162d5
commit
4d92e20e81
11 changed files with 195 additions and 12 deletions
2
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_13.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_13.py
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
exit(code=2)
|
||||
|
2
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_14.py
vendored
Normal file
2
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_14.py
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
code = {"code": 2}
|
||||
exit(**code)
|
6
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_15.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_15.py
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
success = True
|
||||
if success:
|
||||
code = 0
|
||||
else:
|
||||
code = 1
|
||||
exit(code)
|
23
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_16.py
vendored
Normal file
23
crates/ruff_linter/resources/test/fixtures/pylint/sys_exit_alias_16.py
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
def test_case_1():
|
||||
# comments preserved with a positional argument
|
||||
exit( # comment
|
||||
1, # 2
|
||||
) # 3
|
||||
|
||||
|
||||
def test_case_2():
|
||||
# comments preserved with a single keyword argument
|
||||
exit( # comment
|
||||
code=1, # 2
|
||||
) # 3
|
||||
|
||||
|
||||
def test_case_3():
|
||||
# no diagnostic for multiple arguments
|
||||
exit(2, 3, 4)
|
||||
|
||||
|
||||
def test_case_4():
|
||||
# this should now be fixable
|
||||
codes = [1]
|
||||
exit(*codes)
|
|
@ -900,7 +900,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||
pylint::rules::unnecessary_direct_lambda_call(checker, expr, func);
|
||||
}
|
||||
if checker.enabled(Rule::SysExitAlias) {
|
||||
pylint::rules::sys_exit_alias(checker, func);
|
||||
pylint::rules::sys_exit_alias(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::BadOpenMode) {
|
||||
pylint::rules::bad_open_mode(checker, call);
|
||||
|
|
|
@ -64,6 +64,10 @@ mod tests {
|
|||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_10.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_11.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_12.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_13.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_14.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_15.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_16.py"))]
|
||||
#[test_case(Rule::ContinueInFinally, Path::new("continue_in_finally.py"))]
|
||||
#[test_case(Rule::GlobalStatement, Path::new("global_statement.py"))]
|
||||
#[test_case(
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `exit()` and `quit()`.
|
||||
|
@ -56,8 +56,8 @@ impl Violation for SysExitAlias {
|
|||
}
|
||||
|
||||
/// PLR1722
|
||||
pub(crate) fn sys_exit_alias(checker: &Checker, func: &Expr) {
|
||||
let Some(builtin) = checker.semantic().resolve_builtin_symbol(func) else {
|
||||
pub(crate) fn sys_exit_alias(checker: &Checker, call: &ExprCall) {
|
||||
let Some(builtin) = checker.semantic().resolve_builtin_symbol(&call.func) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(builtin, "exit" | "quit") {
|
||||
|
@ -67,16 +67,35 @@ pub(crate) fn sys_exit_alias(checker: &Checker, func: &Expr) {
|
|||
SysExitAlias {
|
||||
name: builtin.to_string(),
|
||||
},
|
||||
func.range(),
|
||||
call.func.range(),
|
||||
);
|
||||
|
||||
let has_star_kwargs = call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|kwarg| kwarg.arg.is_none());
|
||||
// only one optional argument allowed, and we can't convert **kwargs
|
||||
if call.arguments.len() > 1 || has_star_kwargs {
|
||||
checker.report_diagnostic(diagnostic);
|
||||
return;
|
||||
};
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("sys", "exit"),
|
||||
func.start(),
|
||||
call.func.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let reference_edit = Edit::range_replacement(binding, func.range());
|
||||
Ok(Fix::unsafe_edits(import_edit, [reference_edit]))
|
||||
let reference_edit = Edit::range_replacement(binding, call.func.range());
|
||||
let mut edits = vec![reference_edit];
|
||||
if let Some(kwarg) = call.arguments.find_keyword("code") {
|
||||
edits.push(Edit::range_replacement(
|
||||
checker.source()[kwarg.value.range()].to_string(),
|
||||
kwarg.range,
|
||||
));
|
||||
};
|
||||
Ok(Fix::unsafe_edits(import_edit, edits))
|
||||
});
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
sys_exit_alias_13.py:1:1: PLR1722 [*] Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
1 | exit(code=2)
|
||||
| ^^^^ PLR1722
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-exit(code=2)
|
||||
1 |+import sys
|
||||
2 |+sys.exit(2)
|
||||
2 3 |
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
sys_exit_alias_14.py:2:1: PLR1722 Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
1 | code = {"code": 2}
|
||||
2 | exit(**code)
|
||||
| ^^^^ PLR1722
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
sys_exit_alias_15.py:6:1: PLR1722 [*] Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
4 | else:
|
||||
5 | code = 1
|
||||
6 | exit(code)
|
||||
| ^^^^ PLR1722
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+import sys
|
||||
1 2 | success = True
|
||||
2 3 | if success:
|
||||
3 4 | code = 0
|
||||
4 5 | else:
|
||||
5 6 | code = 1
|
||||
6 |-exit(code)
|
||||
7 |+sys.exit(code)
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
sys_exit_alias_16.py:3:5: PLR1722 [*] Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
1 | def test_case_1():
|
||||
2 | # comments preserved with a positional argument
|
||||
3 | exit( # comment
|
||||
| ^^^^ PLR1722
|
||||
4 | 1, # 2
|
||||
5 | ) # 3
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+import sys
|
||||
1 2 | def test_case_1():
|
||||
2 3 | # comments preserved with a positional argument
|
||||
3 |- exit( # comment
|
||||
4 |+ sys.exit( # comment
|
||||
4 5 | 1, # 2
|
||||
5 6 | ) # 3
|
||||
6 7 |
|
||||
|
||||
sys_exit_alias_16.py:10:5: PLR1722 [*] Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
8 | def test_case_2():
|
||||
9 | # comments preserved with a single keyword argument
|
||||
10 | exit( # comment
|
||||
| ^^^^ PLR1722
|
||||
11 | code=1, # 2
|
||||
12 | ) # 3
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+import sys
|
||||
1 2 | def test_case_1():
|
||||
2 3 | # comments preserved with a positional argument
|
||||
3 4 | exit( # comment
|
||||
--------------------------------------------------------------------------------
|
||||
7 8 |
|
||||
8 9 | def test_case_2():
|
||||
9 10 | # comments preserved with a single keyword argument
|
||||
10 |- exit( # comment
|
||||
11 |- code=1, # 2
|
||||
11 |+ sys.exit( # comment
|
||||
12 |+ 1, # 2
|
||||
12 13 | ) # 3
|
||||
13 14 |
|
||||
14 15 |
|
||||
|
||||
sys_exit_alias_16.py:17:5: PLR1722 Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
15 | def test_case_3():
|
||||
16 | # no diagnostic for multiple arguments
|
||||
17 | exit(2, 3, 4)
|
||||
| ^^^^ PLR1722
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
sys_exit_alias_16.py:23:5: PLR1722 [*] Use `sys.exit()` instead of `exit`
|
||||
|
|
||||
21 | # this should now be fixable
|
||||
22 | codes = [1]
|
||||
23 | exit(*codes)
|
||||
| ^^^^ PLR1722
|
||||
|
|
||||
= help: Replace `exit` with `sys.exit()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+import sys
|
||||
1 2 | def test_case_1():
|
||||
2 3 | # comments preserved with a positional argument
|
||||
3 4 | exit( # comment
|
||||
--------------------------------------------------------------------------------
|
||||
20 21 | def test_case_4():
|
||||
21 22 | # this should now be fixable
|
||||
22 23 | codes = [1]
|
||||
23 |- exit(*codes)
|
||||
24 |+ sys.exit(*codes)
|
Loading…
Add table
Add a link
Reference in a new issue