mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:41:23 +00:00
Implement unnecessary-literal-within-dict-call
(C418
) (#3969)
This commit is contained in:
parent
09274307e8
commit
289289bfd3
12 changed files with 249 additions and 4 deletions
10
crates/ruff/resources/test/fixtures/flake8_comprehensions/C418.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_comprehensions/C418.py
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
dict({})
|
||||||
|
dict({'a': 1})
|
||||||
|
dict({'x': 1 for x in range(10)})
|
||||||
|
dict(
|
||||||
|
{'x': 1 for x in range(10)}
|
||||||
|
)
|
||||||
|
|
||||||
|
dict({}, a=1)
|
||||||
|
dict({x: 1 for x in range(1)}, a=1)
|
||||||
|
|
|
@ -2732,7 +2732,7 @@ where
|
||||||
.enabled(Rule::UnnecessaryLiteralWithinTupleCall)
|
.enabled(Rule::UnnecessaryLiteralWithinTupleCall)
|
||||||
{
|
{
|
||||||
flake8_comprehensions::rules::unnecessary_literal_within_tuple_call(
|
flake8_comprehensions::rules::unnecessary_literal_within_tuple_call(
|
||||||
self, expr, func, args,
|
self, expr, func, args, keywords,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self
|
if self
|
||||||
|
@ -2741,7 +2741,16 @@ where
|
||||||
.enabled(Rule::UnnecessaryLiteralWithinListCall)
|
.enabled(Rule::UnnecessaryLiteralWithinListCall)
|
||||||
{
|
{
|
||||||
flake8_comprehensions::rules::unnecessary_literal_within_list_call(
|
flake8_comprehensions::rules::unnecessary_literal_within_list_call(
|
||||||
self, expr, func, args,
|
self, expr, func, args, keywords,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::UnnecessaryLiteralWithinDictCall)
|
||||||
|
{
|
||||||
|
flake8_comprehensions::rules::unnecessary_literal_within_dict_call(
|
||||||
|
self, expr, func, args, keywords,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.settings.rules.enabled(Rule::UnnecessaryListCall) {
|
if self.settings.rules.enabled(Rule::UnnecessaryListCall) {
|
||||||
|
|
|
@ -263,6 +263,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Flake8Comprehensions, "15") => Rule::UnnecessarySubscriptReversal,
|
(Flake8Comprehensions, "15") => Rule::UnnecessarySubscriptReversal,
|
||||||
(Flake8Comprehensions, "16") => Rule::UnnecessaryComprehension,
|
(Flake8Comprehensions, "16") => Rule::UnnecessaryComprehension,
|
||||||
(Flake8Comprehensions, "17") => Rule::UnnecessaryMap,
|
(Flake8Comprehensions, "17") => Rule::UnnecessaryMap,
|
||||||
|
(Flake8Comprehensions, "18") => Rule::UnnecessaryLiteralWithinDictCall,
|
||||||
|
|
||||||
// flake8-debugger
|
// flake8-debugger
|
||||||
(Flake8Debugger, "0") => Rule::Debugger,
|
(Flake8Debugger, "0") => Rule::Debugger,
|
||||||
|
|
|
@ -244,6 +244,7 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal,
|
rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal,
|
||||||
rules::flake8_comprehensions::rules::UnnecessaryComprehension,
|
rules::flake8_comprehensions::rules::UnnecessaryComprehension,
|
||||||
rules::flake8_comprehensions::rules::UnnecessaryMap,
|
rules::flake8_comprehensions::rules::UnnecessaryMap,
|
||||||
|
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall,
|
||||||
// flake8-debugger
|
// flake8-debugger
|
||||||
rules::flake8_debugger::rules::Debugger,
|
rules::flake8_debugger::rules::Debugger,
|
||||||
// mccabe
|
// mccabe
|
||||||
|
|
|
@ -1173,3 +1173,31 @@ pub fn fix_unnecessary_map(
|
||||||
bail!("Should have two arguments");
|
bail!("Should have two arguments");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (C418) Convert `dict({"a": 1})` to `{"a": 1}`
|
||||||
|
pub fn fix_unnecessary_literal_within_dict_call(
|
||||||
|
locator: &Locator,
|
||||||
|
stylist: &Stylist,
|
||||||
|
expr: &rustpython_parser::ast::Expr,
|
||||||
|
) -> Result<Edit> {
|
||||||
|
let module_text = locator.slice(expr);
|
||||||
|
let mut tree = match_module(module_text)?;
|
||||||
|
let mut body = match_expr(&mut tree)?;
|
||||||
|
let call = match_call(body)?;
|
||||||
|
let arg = match_arg(call)?;
|
||||||
|
|
||||||
|
body.value = arg.value.clone();
|
||||||
|
|
||||||
|
let mut state = CodegenState {
|
||||||
|
default_newline: &stylist.line_ending(),
|
||||||
|
default_indent: stylist.indentation(),
|
||||||
|
..CodegenState::default()
|
||||||
|
};
|
||||||
|
tree.codegen(&mut state);
|
||||||
|
|
||||||
|
Ok(Edit::replacement(
|
||||||
|
state.to_string(),
|
||||||
|
expr.location,
|
||||||
|
expr.end_location.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ mod tests {
|
||||||
#[test_case(Rule::UnnecessarySubscriptReversal, Path::new("C415.py"); "C415")]
|
#[test_case(Rule::UnnecessarySubscriptReversal, Path::new("C415.py"); "C415")]
|
||||||
#[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"); "C416")]
|
#[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"); "C416")]
|
||||||
#[test_case(Rule::UnnecessaryMap, Path::new("C417.py"); "C417")]
|
#[test_case(Rule::UnnecessaryMap, Path::new("C417.py"); "C417")]
|
||||||
|
#[test_case(Rule::UnnecessaryLiteralWithinDictCall, Path::new("C418.py"); "C418")]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
@ -20,6 +20,9 @@ pub use unnecessary_list_comprehension_set::{
|
||||||
};
|
};
|
||||||
pub use unnecessary_literal_dict::{unnecessary_literal_dict, UnnecessaryLiteralDict};
|
pub use unnecessary_literal_dict::{unnecessary_literal_dict, UnnecessaryLiteralDict};
|
||||||
pub use unnecessary_literal_set::{unnecessary_literal_set, UnnecessaryLiteralSet};
|
pub use unnecessary_literal_set::{unnecessary_literal_set, UnnecessaryLiteralSet};
|
||||||
|
pub use unnecessary_literal_within_dict_call::{
|
||||||
|
unnecessary_literal_within_dict_call, UnnecessaryLiteralWithinDictCall,
|
||||||
|
};
|
||||||
pub use unnecessary_literal_within_list_call::{
|
pub use unnecessary_literal_within_list_call::{
|
||||||
unnecessary_literal_within_list_call, UnnecessaryLiteralWithinListCall,
|
unnecessary_literal_within_list_call, UnnecessaryLiteralWithinListCall,
|
||||||
};
|
};
|
||||||
|
@ -44,6 +47,7 @@ mod unnecessary_list_comprehension_dict;
|
||||||
mod unnecessary_list_comprehension_set;
|
mod unnecessary_list_comprehension_set;
|
||||||
mod unnecessary_literal_dict;
|
mod unnecessary_literal_dict;
|
||||||
mod unnecessary_literal_set;
|
mod unnecessary_literal_set;
|
||||||
|
mod unnecessary_literal_within_dict_call;
|
||||||
mod unnecessary_literal_within_list_call;
|
mod unnecessary_literal_within_list_call;
|
||||||
mod unnecessary_literal_within_tuple_call;
|
mod unnecessary_literal_within_tuple_call;
|
||||||
mod unnecessary_map;
|
mod unnecessary_map;
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::types::Range;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::AsRule;
|
||||||
|
use crate::rules::flake8_comprehensions::fixes;
|
||||||
|
|
||||||
|
use super::helpers;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DictKind {
|
||||||
|
Literal,
|
||||||
|
Comprehension,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DictKind {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DictKind::Literal => fmt.write_str("literal"),
|
||||||
|
DictKind::Comprehension => fmt.write_str("comprehension"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for `dict` calls that take unnecessary `dict` literals or `dict`
|
||||||
|
/// comprehensions as arguments.
|
||||||
|
///
|
||||||
|
/// ## Why is it bad?
|
||||||
|
/// It's unnecessary to wrap a `dict` literal or comprehension within a `dict`
|
||||||
|
/// call, since the literal or comprehension syntax already returns a `dict`.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// ```python
|
||||||
|
/// dict({})
|
||||||
|
/// dict({"a": 1})
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// {}
|
||||||
|
/// {"a": 1}
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct UnnecessaryLiteralWithinDictCall {
|
||||||
|
pub kind: DictKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlwaysAutofixableViolation for UnnecessaryLiteralWithinDictCall {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let UnnecessaryLiteralWithinDictCall { kind } = self;
|
||||||
|
format!("Unnecessary `dict` {kind} passed to `dict()` (remove the outer call to `dict()`)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title(&self) -> String {
|
||||||
|
"Remove outer `dict` call".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// C418
|
||||||
|
pub fn unnecessary_literal_within_dict_call(
|
||||||
|
checker: &mut Checker,
|
||||||
|
expr: &Expr,
|
||||||
|
func: &Expr,
|
||||||
|
args: &[Expr],
|
||||||
|
keywords: &[Keyword],
|
||||||
|
) {
|
||||||
|
if !keywords.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(argument) = helpers::first_argument_with_matching_function("dict", func, args) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !checker.ctx.is_builtin("dict") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let argument_kind = match argument {
|
||||||
|
ExprKind::DictComp { .. } => DictKind::Comprehension,
|
||||||
|
ExprKind::Dict { .. } => DictKind::Literal,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let mut diagnostic = Diagnostic::new(
|
||||||
|
UnnecessaryLiteralWithinDictCall {
|
||||||
|
kind: argument_kind,
|
||||||
|
},
|
||||||
|
Range::from(expr),
|
||||||
|
);
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
diagnostic.try_set_fix(|| {
|
||||||
|
fixes::fix_unnecessary_literal_within_dict_call(checker.locator, checker.stylist, expr)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use rustpython_parser::ast::{Expr, ExprKind};
|
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
@ -72,7 +72,11 @@ pub fn unnecessary_literal_within_list_call(
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
func: &Expr,
|
func: &Expr,
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
|
if !keywords.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else {
|
let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use rustpython_parser::ast::{Expr, ExprKind};
|
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
@ -73,7 +73,11 @@ pub fn unnecessary_literal_within_tuple_call(
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
func: &Expr,
|
func: &Expr,
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
|
if !keywords.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some(argument) = helpers::first_argument_with_matching_function("tuple", func, args) else {
|
let Some(argument) = helpers::first_argument_with_matching_function("tuple", func, args) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||||
|
---
|
||||||
|
C418.py:1:1: C418 [*] Unnecessary `dict` literal passed to `dict()` (remove the outer call to `dict()`)
|
||||||
|
|
|
||||||
|
1 | dict({})
|
||||||
|
| ^^^^^^^^ C418
|
||||||
|
2 | dict({'a': 1})
|
||||||
|
3 | dict({'x': 1 for x in range(10)})
|
||||||
|
|
|
||||||
|
= help: Remove outer `dict` call
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
1 |-dict({})
|
||||||
|
1 |+{}
|
||||||
|
2 2 | dict({'a': 1})
|
||||||
|
3 3 | dict({'x': 1 for x in range(10)})
|
||||||
|
4 4 | dict(
|
||||||
|
|
||||||
|
C418.py:2:1: C418 [*] Unnecessary `dict` literal passed to `dict()` (remove the outer call to `dict()`)
|
||||||
|
|
|
||||||
|
2 | dict({})
|
||||||
|
3 | dict({'a': 1})
|
||||||
|
| ^^^^^^^^^^^^^^ C418
|
||||||
|
4 | dict({'x': 1 for x in range(10)})
|
||||||
|
5 | dict(
|
||||||
|
|
|
||||||
|
= help: Remove outer `dict` call
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
1 1 | dict({})
|
||||||
|
2 |-dict({'a': 1})
|
||||||
|
2 |+{'a': 1}
|
||||||
|
3 3 | dict({'x': 1 for x in range(10)})
|
||||||
|
4 4 | dict(
|
||||||
|
5 5 | {'x': 1 for x in range(10)}
|
||||||
|
|
||||||
|
C418.py:3:1: C418 [*] Unnecessary `dict` comprehension passed to `dict()` (remove the outer call to `dict()`)
|
||||||
|
|
|
||||||
|
3 | dict({})
|
||||||
|
4 | dict({'a': 1})
|
||||||
|
5 | dict({'x': 1 for x in range(10)})
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C418
|
||||||
|
6 | dict(
|
||||||
|
7 | {'x': 1 for x in range(10)}
|
||||||
|
|
|
||||||
|
= help: Remove outer `dict` call
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
1 1 | dict({})
|
||||||
|
2 2 | dict({'a': 1})
|
||||||
|
3 |-dict({'x': 1 for x in range(10)})
|
||||||
|
3 |+{'x': 1 for x in range(10)}
|
||||||
|
4 4 | dict(
|
||||||
|
5 5 | {'x': 1 for x in range(10)}
|
||||||
|
6 6 | )
|
||||||
|
|
||||||
|
C418.py:4:1: C418 [*] Unnecessary `dict` comprehension passed to `dict()` (remove the outer call to `dict()`)
|
||||||
|
|
|
||||||
|
4 | dict({'a': 1})
|
||||||
|
5 | dict({'x': 1 for x in range(10)})
|
||||||
|
6 | / dict(
|
||||||
|
7 | | {'x': 1 for x in range(10)}
|
||||||
|
8 | | )
|
||||||
|
| |_^ C418
|
||||||
|
9 |
|
||||||
|
10 | dict({}, a=1)
|
||||||
|
|
|
||||||
|
= help: Remove outer `dict` call
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
1 1 | dict({})
|
||||||
|
2 2 | dict({'a': 1})
|
||||||
|
3 3 | dict({'x': 1 for x in range(10)})
|
||||||
|
4 |-dict(
|
||||||
|
5 |- {'x': 1 for x in range(10)}
|
||||||
|
6 |-)
|
||||||
|
4 |+{'x': 1 for x in range(10)}
|
||||||
|
7 5 |
|
||||||
|
8 6 | dict({}, a=1)
|
||||||
|
9 7 | dict({x: 1 for x in range(1)}, a=1)
|
||||||
|
|
||||||
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -1604,6 +1604,7 @@
|
||||||
"C415",
|
"C415",
|
||||||
"C416",
|
"C416",
|
||||||
"C417",
|
"C417",
|
||||||
|
"C418",
|
||||||
"C9",
|
"C9",
|
||||||
"C90",
|
"C90",
|
||||||
"C901",
|
"C901",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue