mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 20:42:10 +00:00
PIE804
: Prevent keyword arguments duplication (#8450)
This commit is contained in:
parent
6c0068eeec
commit
cb201bc4a5
3 changed files with 142 additions and 44 deletions
|
@ -19,5 +19,8 @@ foo(**{buzz: True})
|
||||||
foo(**{"": True})
|
foo(**{"": True})
|
||||||
foo(**{f"buzz__{bar}": True})
|
foo(**{f"buzz__{bar}": True})
|
||||||
abc(**{"for": 3})
|
abc(**{"for": 3})
|
||||||
|
|
||||||
foo(**{},)
|
foo(**{},)
|
||||||
|
|
||||||
|
# Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
use rustc_hash::FxHashSet;
|
||||||
use ruff_python_ast::{self as ast, Expr};
|
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
|
|
||||||
use ruff_python_stdlib::identifiers::is_identifier;
|
use ruff_python_stdlib::identifiers::is_identifier;
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::fix::edits::{remove_argument, Parentheses};
|
use crate::fix::edits::{remove_argument, Parentheses};
|
||||||
|
@ -41,36 +43,39 @@ use crate::fix::edits::{remove_argument, Parentheses};
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct UnnecessaryDictKwargs;
|
pub struct UnnecessaryDictKwargs;
|
||||||
|
|
||||||
impl AlwaysFixableViolation for UnnecessaryDictKwargs {
|
impl Violation for UnnecessaryDictKwargs {
|
||||||
|
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!("Unnecessary `dict` kwargs")
|
format!("Unnecessary `dict` kwargs")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_title(&self) -> String {
|
fn fix_title(&self) -> Option<String> {
|
||||||
format!("Remove unnecessary kwargs")
|
Some(format!("Remove unnecessary kwargs"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PIE804
|
/// PIE804
|
||||||
pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCall) {
|
pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
for kw in &call.arguments.keywords {
|
let mut duplicate_keywords = None;
|
||||||
// keyword is a spread operator (indicated by None)
|
for keyword in &call.arguments.keywords {
|
||||||
if kw.arg.is_some() {
|
// keyword is a spread operator (indicated by None).
|
||||||
|
if keyword.arg.is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Expr::Dict(ast::ExprDict { keys, values, .. }) = &kw.value else {
|
let Expr::Dict(ast::ExprDict { keys, values, .. }) = &keyword.value else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ex) `foo(**{**bar})`
|
// Ex) `foo(**{**bar})`
|
||||||
if matches!(keys.as_slice(), [None]) {
|
if matches!(keys.as_slice(), [None]) {
|
||||||
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, call.range());
|
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range());
|
||||||
|
|
||||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||||
format!("**{}", checker.locator().slice(values[0].range())),
|
format!("**{}", checker.locator().slice(values[0].range())),
|
||||||
kw.range(),
|
keyword.range(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
@ -87,12 +92,12 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, call.range());
|
let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, keyword.range());
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
remove_argument(
|
remove_argument(
|
||||||
kw,
|
keyword,
|
||||||
&call.arguments,
|
&call.arguments,
|
||||||
Parentheses::Preserve,
|
Parentheses::Preserve,
|
||||||
checker.locator().contents(),
|
checker.locator().contents(),
|
||||||
|
@ -100,6 +105,17 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
|
||||||
.map(Fix::safe_edit)
|
.map(Fix::safe_edit)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Compute the set of duplicate keywords (lazily).
|
||||||
|
if duplicate_keywords.is_none() {
|
||||||
|
duplicate_keywords = Some(duplicates(call));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid fixing if doing so could introduce a duplicate keyword argument.
|
||||||
|
if let Some(duplicate_keywords) = duplicate_keywords.as_ref() {
|
||||||
|
if kwargs
|
||||||
|
.iter()
|
||||||
|
.all(|kwarg| !duplicate_keywords.contains(kwarg))
|
||||||
|
{
|
||||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||||
kwargs
|
kwargs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -108,14 +124,45 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, call: &ast::ExprCal
|
||||||
format!("{}={}", kwarg, checker.locator().slice(value.range()))
|
format!("{}={}", kwarg, checker.locator().slice(value.range()))
|
||||||
})
|
})
|
||||||
.join(", "),
|
.join(", "),
|
||||||
kw.range(),
|
keyword.range(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine the set of keywords that appear in multiple positions (either directly, as in
|
||||||
|
/// `func(x=1)`, or indirectly, as in `func(**{"x": 1})`).
|
||||||
|
fn duplicates(call: &ast::ExprCall) -> FxHashSet<&str> {
|
||||||
|
let mut seen = FxHashSet::with_capacity_and_hasher(
|
||||||
|
call.arguments.keywords.len(),
|
||||||
|
BuildHasherDefault::default(),
|
||||||
|
);
|
||||||
|
let mut duplicates = FxHashSet::with_capacity_and_hasher(
|
||||||
|
call.arguments.keywords.len(),
|
||||||
|
BuildHasherDefault::default(),
|
||||||
|
);
|
||||||
|
for keyword in &call.arguments.keywords {
|
||||||
|
if let Some(name) = &keyword.arg {
|
||||||
|
if !seen.insert(name.as_str()) {
|
||||||
|
duplicates.insert(name.as_str());
|
||||||
|
}
|
||||||
|
} else if let Expr::Dict(ast::ExprDict { keys, .. }) = &keyword.value {
|
||||||
|
for key in keys {
|
||||||
|
if let Some(name) = key.as_ref().and_then(as_kwarg) {
|
||||||
|
if !seen.insert(name) {
|
||||||
|
duplicates.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
duplicates
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `Some` if a key is a valid keyword argument name, or `None` otherwise.
|
/// Return `Some` if a key is a valid keyword argument name, or `None` otherwise.
|
||||||
fn as_kwarg(key: &Expr) -> Option<&str> {
|
fn as_kwarg(key: &Expr) -> Option<&str> {
|
||||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = key {
|
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = key {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_pie/mod.rs
|
||||||
---
|
---
|
||||||
PIE804.py:1:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:1:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
1 | foo(**{"bar": True}) # PIE804
|
1 | foo(**{"bar": True}) # PIE804
|
||||||
| ^^^^^^^^^^^^^^^^^^^^ PIE804
|
| ^^^^^^^^^^^^^^^ PIE804
|
||||||
2 |
|
2 |
|
||||||
3 | foo(**{"r2d2": True}) # PIE804
|
3 | foo(**{"r2d2": True}) # PIE804
|
||||||
|
|
|
|
||||||
|
@ -17,12 +17,12 @@ PIE804.py:1:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
3 3 | foo(**{"r2d2": True}) # PIE804
|
3 3 | foo(**{"r2d2": True}) # PIE804
|
||||||
4 4 |
|
4 4 |
|
||||||
|
|
||||||
PIE804.py:3:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:3:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
1 | foo(**{"bar": True}) # PIE804
|
1 | foo(**{"bar": True}) # PIE804
|
||||||
2 |
|
2 |
|
||||||
3 | foo(**{"r2d2": True}) # PIE804
|
3 | foo(**{"r2d2": True}) # PIE804
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ PIE804
|
| ^^^^^^^^^^^^^^^^ PIE804
|
||||||
4 |
|
4 |
|
||||||
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
||||||
|
|
|
|
||||||
|
@ -37,12 +37,12 @@ PIE804.py:3:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
5 5 | Foo.objects.create(**{"bar": True}) # PIE804
|
5 5 | Foo.objects.create(**{"bar": True}) # PIE804
|
||||||
6 6 |
|
6 6 |
|
||||||
|
|
||||||
PIE804.py:5:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:5:20: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
3 | foo(**{"r2d2": True}) # PIE804
|
3 | foo(**{"r2d2": True}) # PIE804
|
||||||
4 |
|
4 |
|
||||||
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE804
|
| ^^^^^^^^^^^^^^^ PIE804
|
||||||
6 |
|
6 |
|
||||||
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||||
|
|
|
|
||||||
|
@ -58,12 +58,12 @@ PIE804.py:5:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
7 7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
7 7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||||
8 8 |
|
8 8 |
|
||||||
|
|
||||||
PIE804.py:7:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:7:20: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
5 | Foo.objects.create(**{"bar": True}) # PIE804
|
||||||
6 |
|
6 |
|
||||||
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE804
|
| ^^^^^^^^^^^^^^^^^^ PIE804
|
||||||
8 |
|
8 |
|
||||||
9 | Foo.objects.create(**{**bar}) # PIE804
|
9 | Foo.objects.create(**{**bar}) # PIE804
|
||||||
|
|
|
|
||||||
|
@ -79,12 +79,12 @@ PIE804.py:7:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
9 9 | Foo.objects.create(**{**bar}) # PIE804
|
9 9 | Foo.objects.create(**{**bar}) # PIE804
|
||||||
10 10 |
|
10 10 |
|
||||||
|
|
||||||
PIE804.py:9:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:9:20: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||||
8 |
|
8 |
|
||||||
9 | Foo.objects.create(**{**bar}) # PIE804
|
9 | Foo.objects.create(**{**bar}) # PIE804
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE804
|
| ^^^^^^^^^ PIE804
|
||||||
10 |
|
10 |
|
||||||
11 | foo(**{})
|
11 | foo(**{})
|
||||||
|
|
|
|
||||||
|
@ -100,12 +100,12 @@ PIE804.py:9:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
11 11 | foo(**{})
|
11 11 | foo(**{})
|
||||||
12 12 |
|
12 12 |
|
||||||
|
|
||||||
PIE804.py:11:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:11:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
9 | Foo.objects.create(**{**bar}) # PIE804
|
9 | Foo.objects.create(**{**bar}) # PIE804
|
||||||
10 |
|
10 |
|
||||||
11 | foo(**{})
|
11 | foo(**{})
|
||||||
| ^^^^^^^^^ PIE804
|
| ^^^^ PIE804
|
||||||
12 |
|
12 |
|
||||||
13 | foo(**{**data, "foo": "buzz"})
|
13 | foo(**{**data, "foo": "buzz"})
|
||||||
|
|
|
|
||||||
|
@ -121,20 +121,68 @@ PIE804.py:11:1: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
13 13 | foo(**{**data, "foo": "buzz"})
|
13 13 | foo(**{**data, "foo": "buzz"})
|
||||||
14 14 | foo(**buzz)
|
14 14 | foo(**buzz)
|
||||||
|
|
||||||
PIE804.py:23:1: PIE804 [*] Unnecessary `dict` kwargs
|
PIE804.py:22:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
|
||||||
|
20 | foo(**{f"buzz__{bar}": True})
|
||||||
21 | abc(**{"for": 3})
|
21 | abc(**{"for": 3})
|
||||||
22 |
|
22 | foo(**{},)
|
||||||
23 | foo(**{},)
|
| ^^^^ PIE804
|
||||||
|
23 |
|
||||||
|
24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary kwargs
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
19 19 | foo(**{"": True})
|
||||||
|
20 20 | foo(**{f"buzz__{bar}": True})
|
||||||
|
21 21 | abc(**{"for": 3})
|
||||||
|
22 |-foo(**{},)
|
||||||
|
22 |+foo()
|
||||||
|
23 23 |
|
||||||
|
24 24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
25 25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
|
||||||
|
PIE804.py:25:5: PIE804 Unnecessary `dict` kwargs
|
||||||
|
|
|
||||||
|
24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
| ^^^^^^^^^^ PIE804
|
||||||
|
26 | abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary kwargs
|
||||||
|
|
||||||
|
PIE804.py:25:17: PIE804 Unnecessary `dict` kwargs
|
||||||
|
|
|
||||||
|
24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
| ^^^^^^^^^^ PIE804
|
||||||
|
26 | abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary kwargs
|
||||||
|
|
||||||
|
PIE804.py:26:10: PIE804 Unnecessary `dict` kwargs
|
||||||
|
|
|
||||||
|
24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
26 | abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
|
| ^^^^^^^^^^ PIE804
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary kwargs
|
||||||
|
|
||||||
|
PIE804.py:26:22: PIE804 [*] Unnecessary `dict` kwargs
|
||||||
|
|
|
||||||
|
24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
|
25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
|
26 | abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
| ^^^^^^^^^^ PIE804
|
| ^^^^^^^^^^ PIE804
|
||||||
|
|
|
|
||||||
= help: Remove unnecessary kwargs
|
= help: Remove unnecessary kwargs
|
||||||
|
|
||||||
ℹ Safe fix
|
ℹ Safe fix
|
||||||
20 20 | foo(**{f"buzz__{bar}": True})
|
23 23 |
|
||||||
21 21 | abc(**{"for": 3})
|
24 24 | # Duplicated key names won't be fixed, to avoid syntax errors.
|
||||||
22 22 |
|
25 25 | abc(**{'a': b}, **{'a': c}) # PIE804
|
||||||
23 |-foo(**{},)
|
26 |-abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||||
23 |+foo()
|
26 |+abc(a=1, **{'a': c}, b=c) # PIE804
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue