mirror of https://github.com/astral-sh/ruff
Automatically remove duplicate dictionary keys (#1710)
For now, to be safe, we're only removing keys with duplicate _values_. See: #1647.
This commit is contained in:
parent
98856e05d6
commit
4de6c26ff9
|
|
@ -551,8 +551,8 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||||
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
|
| F524 | StringDotFormatMissingArguments | '...'.format(...) is missing argument(s) for placeholder(s): ... | |
|
||||||
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
|
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
|
||||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders | 🛠 |
|
| F541 | FStringMissingPlaceholders | f-string without any placeholders | 🛠 |
|
||||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
|
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal `...` repeated | 🛠 |
|
||||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
|
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | 🛠 |
|
||||||
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
|
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
|
||||||
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
|
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
|
||||||
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
|
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,41 @@ x = {
|
||||||
b"123": 1,
|
b"123": 1,
|
||||||
b"123": 4,
|
b"123": 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
"a": 1,
|
||||||
|
"a": 2,
|
||||||
|
"a": 3,
|
||||||
|
"a": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
"a": 1,
|
||||||
|
"a": 2,
|
||||||
|
"a": 3,
|
||||||
|
"a": 3,
|
||||||
|
"a": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
"a": 1,
|
||||||
|
"a": 1,
|
||||||
|
"a": 2,
|
||||||
|
"a": 3,
|
||||||
|
"a": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
a: 1,
|
||||||
|
"a": 1,
|
||||||
|
a: 1,
|
||||||
|
"a": 2,
|
||||||
|
a: 2,
|
||||||
|
"a": 3,
|
||||||
|
a: 3,
|
||||||
|
"a": 3,
|
||||||
|
a: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {"a": 1, "a": 1}
|
||||||
|
x = {"a": 1, "b": 2, "a": 1}
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,41 @@ x = {
|
||||||
a: 2,
|
a: 2,
|
||||||
b: 3,
|
b: 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
a: 1,
|
||||||
|
a: 2,
|
||||||
|
a: 3,
|
||||||
|
a: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
a: 1,
|
||||||
|
a: 2,
|
||||||
|
a: 3,
|
||||||
|
a: 3,
|
||||||
|
a: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
a: 1,
|
||||||
|
a: 1,
|
||||||
|
a: 2,
|
||||||
|
a: 3,
|
||||||
|
a: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {
|
||||||
|
a: 1,
|
||||||
|
"a": 1,
|
||||||
|
a: 1,
|
||||||
|
"a": 2,
|
||||||
|
a: 2,
|
||||||
|
"a": 3,
|
||||||
|
a: 3,
|
||||||
|
"a": 3,
|
||||||
|
a: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
x = {a: 1, a: 1}
|
||||||
|
x = {a: 1, b: 2, a: 1}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,394 @@
|
||||||
|
//! Compare two AST nodes for equality, ignoring locations.
|
||||||
|
|
||||||
|
use rustpython_ast::{Arg, Arguments, Comprehension, Expr, ExprKind, Keyword};
|
||||||
|
|
||||||
|
/// Returns `true` if the two `Expr` are equal, ignoring locations.
|
||||||
|
pub fn expr(a: &Expr, b: &Expr) -> bool {
|
||||||
|
match (&a.node, &b.node) {
|
||||||
|
(
|
||||||
|
ExprKind::BoolOp {
|
||||||
|
op: op_a,
|
||||||
|
values: values_a,
|
||||||
|
},
|
||||||
|
ExprKind::BoolOp {
|
||||||
|
op: op_b,
|
||||||
|
values: values_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
op_a == op_b
|
||||||
|
&& values_a.len() == values_b.len()
|
||||||
|
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::NamedExpr {
|
||||||
|
target: target_a,
|
||||||
|
value: value_a,
|
||||||
|
},
|
||||||
|
ExprKind::NamedExpr {
|
||||||
|
target: target_b,
|
||||||
|
value: value_b,
|
||||||
|
},
|
||||||
|
) => expr(target_a, target_b) && expr(value_a, value_b),
|
||||||
|
(
|
||||||
|
ExprKind::BinOp {
|
||||||
|
left: left_a,
|
||||||
|
op: op_a,
|
||||||
|
right: right_a,
|
||||||
|
},
|
||||||
|
ExprKind::BinOp {
|
||||||
|
left: left_b,
|
||||||
|
op: op_b,
|
||||||
|
right: right_b,
|
||||||
|
},
|
||||||
|
) => op_a == op_b && expr(left_a, left_b) && expr(right_a, right_b),
|
||||||
|
(
|
||||||
|
ExprKind::UnaryOp {
|
||||||
|
op: op_a,
|
||||||
|
operand: operand_a,
|
||||||
|
},
|
||||||
|
ExprKind::UnaryOp {
|
||||||
|
op: op_b,
|
||||||
|
operand: operand_b,
|
||||||
|
},
|
||||||
|
) => op_a == op_b && expr(operand_a, operand_b),
|
||||||
|
(
|
||||||
|
ExprKind::Lambda {
|
||||||
|
args: args_a,
|
||||||
|
body: body_a,
|
||||||
|
},
|
||||||
|
ExprKind::Lambda {
|
||||||
|
args: args_b,
|
||||||
|
body: body_b,
|
||||||
|
},
|
||||||
|
) => expr(body_a, body_b) && arguments(args_a, args_b),
|
||||||
|
(
|
||||||
|
ExprKind::IfExp {
|
||||||
|
test: test_a,
|
||||||
|
body: body_a,
|
||||||
|
orelse: orelse_a,
|
||||||
|
},
|
||||||
|
ExprKind::IfExp {
|
||||||
|
test: test_b,
|
||||||
|
body: body_b,
|
||||||
|
orelse: orelse_b,
|
||||||
|
},
|
||||||
|
) => expr(test_a, test_b) && expr(body_a, body_b) && expr(orelse_a, orelse_b),
|
||||||
|
(
|
||||||
|
ExprKind::Dict {
|
||||||
|
keys: keys_a,
|
||||||
|
values: values_a,
|
||||||
|
},
|
||||||
|
ExprKind::Dict {
|
||||||
|
keys: keys_b,
|
||||||
|
values: values_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
keys_a.len() == keys_b.len()
|
||||||
|
&& values_a.len() == values_b.len()
|
||||||
|
&& keys_a.iter().zip(keys_b).all(|(a, b)| expr(a, b))
|
||||||
|
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(ExprKind::Set { elts: elts_a }, ExprKind::Set { elts: elts_b }) => {
|
||||||
|
elts_a.len() == elts_b.len() && elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::ListComp {
|
||||||
|
elt: elt_a,
|
||||||
|
generators: generators_a,
|
||||||
|
},
|
||||||
|
ExprKind::ListComp {
|
||||||
|
elt: elt_b,
|
||||||
|
generators: generators_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(elt_a, elt_b)
|
||||||
|
&& generators_a.len() == generators_b.len()
|
||||||
|
&& generators_a
|
||||||
|
.iter()
|
||||||
|
.zip(generators_b)
|
||||||
|
.all(|(a, b)| comprehension(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::SetComp {
|
||||||
|
elt: elt_a,
|
||||||
|
generators: generators_a,
|
||||||
|
},
|
||||||
|
ExprKind::SetComp {
|
||||||
|
elt: elt_b,
|
||||||
|
generators: generators_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(elt_a, elt_b)
|
||||||
|
&& generators_a.len() == generators_b.len()
|
||||||
|
&& generators_a
|
||||||
|
.iter()
|
||||||
|
.zip(generators_b)
|
||||||
|
.all(|(a, b)| comprehension(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::DictComp {
|
||||||
|
key: key_a,
|
||||||
|
value: value_a,
|
||||||
|
generators: generators_a,
|
||||||
|
},
|
||||||
|
ExprKind::DictComp {
|
||||||
|
key: key_b,
|
||||||
|
value: value_b,
|
||||||
|
generators: generators_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(key_a, key_b)
|
||||||
|
&& expr(value_a, value_b)
|
||||||
|
&& generators_a.len() == generators_b.len()
|
||||||
|
&& generators_a
|
||||||
|
.iter()
|
||||||
|
.zip(generators_b)
|
||||||
|
.all(|(a, b)| comprehension(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::GeneratorExp {
|
||||||
|
elt: elt_a,
|
||||||
|
generators: generators_a,
|
||||||
|
},
|
||||||
|
ExprKind::GeneratorExp {
|
||||||
|
elt: elt_b,
|
||||||
|
generators: generators_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(elt_a, elt_b)
|
||||||
|
&& generators_a.len() == generators_b.len()
|
||||||
|
&& generators_a
|
||||||
|
.iter()
|
||||||
|
.zip(generators_b)
|
||||||
|
.all(|(a, b)| comprehension(a, b))
|
||||||
|
}
|
||||||
|
(ExprKind::Await { value: value_a }, ExprKind::Await { value: value_b }) => {
|
||||||
|
expr(value_a, value_b)
|
||||||
|
}
|
||||||
|
(ExprKind::Yield { value: value_a }, ExprKind::Yield { value: value_b }) => {
|
||||||
|
option_expr(value_a.as_deref(), value_b.as_deref())
|
||||||
|
}
|
||||||
|
(ExprKind::YieldFrom { value: value_a }, ExprKind::YieldFrom { value: value_b }) => {
|
||||||
|
expr(value_a, value_b)
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::Compare {
|
||||||
|
left: left_a,
|
||||||
|
ops: ops_a,
|
||||||
|
comparators: comparators_a,
|
||||||
|
},
|
||||||
|
ExprKind::Compare {
|
||||||
|
left: left_b,
|
||||||
|
ops: ops_b,
|
||||||
|
comparators: comparators_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(left_a, left_b)
|
||||||
|
&& ops_a == ops_b
|
||||||
|
&& comparators_a.len() == comparators_b.len()
|
||||||
|
&& comparators_a
|
||||||
|
.iter()
|
||||||
|
.zip(comparators_b)
|
||||||
|
.all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::Call {
|
||||||
|
func: func_a,
|
||||||
|
args: args_a,
|
||||||
|
keywords: keywords_a,
|
||||||
|
},
|
||||||
|
ExprKind::Call {
|
||||||
|
func: func_b,
|
||||||
|
args: args_b,
|
||||||
|
keywords: keywords_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(func_a, func_b)
|
||||||
|
&& args_a.len() == args_b.len()
|
||||||
|
&& args_a.iter().zip(args_b).all(|(a, b)| expr(a, b))
|
||||||
|
&& keywords_a.len() == keywords_b.len()
|
||||||
|
&& keywords_a
|
||||||
|
.iter()
|
||||||
|
.zip(keywords_b)
|
||||||
|
.all(|(a, b)| keyword(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::FormattedValue {
|
||||||
|
value: value_a,
|
||||||
|
conversion: conversion_a,
|
||||||
|
format_spec: format_spec_a,
|
||||||
|
},
|
||||||
|
ExprKind::FormattedValue {
|
||||||
|
value: value_b,
|
||||||
|
conversion: conversion_b,
|
||||||
|
format_spec: format_spec_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
expr(value_a, value_b)
|
||||||
|
&& conversion_a == conversion_b
|
||||||
|
&& option_expr(format_spec_a.as_deref(), format_spec_b.as_deref())
|
||||||
|
}
|
||||||
|
(ExprKind::JoinedStr { values: values_a }, ExprKind::JoinedStr { values: values_b }) => {
|
||||||
|
values_a.len() == values_b.len()
|
||||||
|
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::Constant {
|
||||||
|
value: value_a,
|
||||||
|
kind: kind_a,
|
||||||
|
},
|
||||||
|
ExprKind::Constant {
|
||||||
|
value: value_b,
|
||||||
|
kind: kind_b,
|
||||||
|
},
|
||||||
|
) => value_a == value_b && kind_a == kind_b,
|
||||||
|
(
|
||||||
|
ExprKind::Attribute {
|
||||||
|
value: value_a,
|
||||||
|
attr: attr_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::Attribute {
|
||||||
|
value: value_b,
|
||||||
|
attr: attr_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => attr_a == attr_b && ctx_a == ctx_b && expr(value_a, value_b),
|
||||||
|
(
|
||||||
|
ExprKind::Subscript {
|
||||||
|
value: value_a,
|
||||||
|
slice: slice_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::Subscript {
|
||||||
|
value: value_b,
|
||||||
|
slice: slice_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => ctx_a == ctx_b && expr(value_a, value_b) && expr(slice_a, slice_b),
|
||||||
|
(
|
||||||
|
ExprKind::Starred {
|
||||||
|
value: value_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::Starred {
|
||||||
|
value: value_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => ctx_a == ctx_b && expr(value_a, value_b),
|
||||||
|
(
|
||||||
|
ExprKind::Name {
|
||||||
|
id: id_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::Name {
|
||||||
|
id: id_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => id_a == id_b && ctx_a == ctx_b,
|
||||||
|
(
|
||||||
|
ExprKind::List {
|
||||||
|
elts: elts_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::List {
|
||||||
|
elts: elts_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
ctx_a == ctx_b
|
||||||
|
&& elts_a.len() == elts_b.len()
|
||||||
|
&& elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::Tuple {
|
||||||
|
elts: elts_a,
|
||||||
|
ctx: ctx_a,
|
||||||
|
},
|
||||||
|
ExprKind::Tuple {
|
||||||
|
elts: elts_b,
|
||||||
|
ctx: ctx_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
ctx_a == ctx_b
|
||||||
|
&& elts_a.len() == elts_b.len()
|
||||||
|
&& elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
(
|
||||||
|
ExprKind::Slice {
|
||||||
|
lower: lower_a,
|
||||||
|
upper: upper_a,
|
||||||
|
step: step_a,
|
||||||
|
},
|
||||||
|
ExprKind::Slice {
|
||||||
|
lower: lower_b,
|
||||||
|
upper: upper_b,
|
||||||
|
step: step_b,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
option_expr(lower_a.as_deref(), lower_b.as_deref())
|
||||||
|
&& option_expr(upper_a.as_deref(), upper_b.as_deref())
|
||||||
|
&& option_expr(step_a.as_deref(), step_b.as_deref())
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_expr(a: Option<&Expr>, b: Option<&Expr>) -> bool {
|
||||||
|
match (a, b) {
|
||||||
|
(Some(a), Some(b)) => expr(a, b),
|
||||||
|
(None, None) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arguments(a: &Arguments, b: &Arguments) -> bool {
|
||||||
|
a.posonlyargs.len() == b.posonlyargs.len()
|
||||||
|
&& a.posonlyargs
|
||||||
|
.iter()
|
||||||
|
.zip(b.posonlyargs.iter())
|
||||||
|
.all(|(a, b)| arg(a, b))
|
||||||
|
&& a.args.len() == b.args.len()
|
||||||
|
&& a.args.iter().zip(b.args.iter()).all(|(a, b)| arg(a, b))
|
||||||
|
&& option_arg(a.vararg.as_deref(), b.vararg.as_deref())
|
||||||
|
&& a.kwonlyargs.len() == b.kwonlyargs.len()
|
||||||
|
&& a.kwonlyargs
|
||||||
|
.iter()
|
||||||
|
.zip(b.kwonlyargs.iter())
|
||||||
|
.all(|(a, b)| arg(a, b))
|
||||||
|
&& a.kw_defaults.len() == b.kw_defaults.len()
|
||||||
|
&& a.kw_defaults
|
||||||
|
.iter()
|
||||||
|
.zip(b.kw_defaults.iter())
|
||||||
|
.all(|(a, b)| expr(a, b))
|
||||||
|
&& option_arg(a.kwarg.as_deref(), b.kwarg.as_deref())
|
||||||
|
&& a.defaults.len() == b.defaults.len()
|
||||||
|
&& a.defaults
|
||||||
|
.iter()
|
||||||
|
.zip(b.defaults.iter())
|
||||||
|
.all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg(a: &Arg, b: &Arg) -> bool {
|
||||||
|
a.node.arg == b.node.arg
|
||||||
|
&& option_expr(a.node.annotation.as_deref(), b.node.annotation.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_arg(a: Option<&Arg>, b: Option<&Arg>) -> bool {
|
||||||
|
match (a, b) {
|
||||||
|
(Some(a), Some(b)) => arg(a, b),
|
||||||
|
(None, None) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keyword(a: &Keyword, b: &Keyword) -> bool {
|
||||||
|
a.node.arg == b.node.arg && expr(&a.node.value, &b.node.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comprehension(a: &Comprehension, b: &Comprehension) -> bool {
|
||||||
|
expr(&a.iter, &b.iter)
|
||||||
|
&& expr(&a.target, &b.target)
|
||||||
|
&& a.ifs.len() == b.ifs.len()
|
||||||
|
&& a.ifs.iter().zip(b.ifs.iter()).all(|(a, b)| expr(a, b))
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod branch_detection;
|
pub mod branch_detection;
|
||||||
pub mod cast;
|
pub mod cast;
|
||||||
|
pub mod cmp;
|
||||||
pub mod function_type;
|
pub mod function_type;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
|
|
|
||||||
|
|
@ -2385,15 +2385,11 @@ where
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExprKind::Dict { keys, .. } => {
|
ExprKind::Dict { keys, values } => {
|
||||||
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
|
if self.settings.enabled.contains(&CheckCode::F601)
|
||||||
let check_repeated_variables = self.settings.enabled.contains(&CheckCode::F602);
|
|| self.settings.enabled.contains(&CheckCode::F602)
|
||||||
if check_repeated_literals || check_repeated_variables {
|
{
|
||||||
self.checks.extend(pyflakes::checks::repeated_keys(
|
pyflakes::plugins::repeated_keys(self, keys, values);
|
||||||
keys,
|
|
||||||
check_repeated_literals,
|
|
||||||
check_repeated_variables,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExprKind::Yield { .. } => {
|
ExprKind::Yield { .. } => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
use rustpython_parser::ast::{
|
use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind};
|
||||||
Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::ast::helpers::except_range;
|
use crate::ast::helpers::except_range;
|
||||||
use crate::ast::types::{Binding, Range, Scope, ScopeKind};
|
use crate::ast::types::{Binding, Range, Scope, ScopeKind};
|
||||||
|
|
@ -70,59 +68,6 @@ pub fn default_except_not_last(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
enum DictionaryKey<'a> {
|
|
||||||
Constant(&'a Constant),
|
|
||||||
Variable(&'a str),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
|
|
||||||
match &expr.node {
|
|
||||||
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value)),
|
|
||||||
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// F601, F602
|
|
||||||
pub fn repeated_keys(
|
|
||||||
keys: &[Expr],
|
|
||||||
check_repeated_literals: bool,
|
|
||||||
check_repeated_variables: bool,
|
|
||||||
) -> Vec<Check> {
|
|
||||||
let mut checks: Vec<Check> = vec![];
|
|
||||||
|
|
||||||
let num_keys = keys.len();
|
|
||||||
for i in 0..num_keys {
|
|
||||||
let k1 = &keys[i];
|
|
||||||
let v1 = convert_to_value(k1);
|
|
||||||
for k2 in keys.iter().take(num_keys).skip(i + 1) {
|
|
||||||
let v2 = convert_to_value(k2);
|
|
||||||
match (&v1, &v2) {
|
|
||||||
(Some(DictionaryKey::Constant(v1)), Some(DictionaryKey::Constant(v2))) => {
|
|
||||||
if check_repeated_literals && v1 == v2 {
|
|
||||||
checks.push(Check::new(
|
|
||||||
violations::MultiValueRepeatedKeyLiteral,
|
|
||||||
Range::from_located(k2),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Some(DictionaryKey::Variable(v1)), Some(DictionaryKey::Variable(v2))) => {
|
|
||||||
if check_repeated_variables && v1 == v2 {
|
|
||||||
checks.push(Check::new(
|
|
||||||
violations::MultiValueRepeatedKeyVariable((*v2).to_string()),
|
|
||||||
Range::from_located(k2),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checks
|
|
||||||
}
|
|
||||||
|
|
||||||
/// F621, F622
|
/// F621, F622
|
||||||
pub fn starred_expressions(
|
pub fn starred_expressions(
|
||||||
elts: &[Expr],
|
elts: &[Expr],
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ pub use if_tuple::if_tuple;
|
||||||
pub use invalid_literal_comparisons::invalid_literal_comparison;
|
pub use invalid_literal_comparisons::invalid_literal_comparison;
|
||||||
pub use invalid_print_syntax::invalid_print_syntax;
|
pub use invalid_print_syntax::invalid_print_syntax;
|
||||||
pub use raise_not_implemented::raise_not_implemented;
|
pub use raise_not_implemented::raise_not_implemented;
|
||||||
|
pub use repeated_keys::repeated_keys;
|
||||||
pub(crate) use strings::{
|
pub(crate) use strings::{
|
||||||
percent_format_expected_mapping, percent_format_expected_sequence,
|
percent_format_expected_mapping, percent_format_expected_sequence,
|
||||||
percent_format_extra_named_arguments, percent_format_missing_arguments,
|
percent_format_extra_named_arguments, percent_format_missing_arguments,
|
||||||
|
|
@ -21,6 +22,7 @@ mod if_tuple;
|
||||||
mod invalid_literal_comparisons;
|
mod invalid_literal_comparisons;
|
||||||
mod invalid_print_syntax;
|
mod invalid_print_syntax;
|
||||||
mod raise_not_implemented;
|
mod raise_not_implemented;
|
||||||
|
mod repeated_keys;
|
||||||
mod strings;
|
mod strings;
|
||||||
mod unused_annotation;
|
mod unused_annotation;
|
||||||
mod unused_variable;
|
mod unused_variable;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
use std::hash::{BuildHasherDefault, Hash};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use rustpython_ast::{Expr, ExprKind};
|
||||||
|
|
||||||
|
use crate::ast::cmp;
|
||||||
|
use crate::ast::helpers::unparse_expr;
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::autofix::Fix;
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::{Check, CheckCode};
|
||||||
|
use crate::source_code_style::SourceCodeStyleDetector;
|
||||||
|
use crate::violations;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
|
enum DictionaryKey<'a> {
|
||||||
|
Constant(String),
|
||||||
|
Variable(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_dictionary_key<'a>(
|
||||||
|
expr: &'a Expr,
|
||||||
|
stylist: &SourceCodeStyleDetector,
|
||||||
|
) -> Option<DictionaryKey<'a>> {
|
||||||
|
match &expr.node {
|
||||||
|
ExprKind::Constant { .. } => Some(DictionaryKey::Constant(unparse_expr(expr, stylist))),
|
||||||
|
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// F601, F602
|
||||||
|
pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
|
||||||
|
// Generate a map from key to (index, value).
|
||||||
|
let mut seen: FxHashMap<DictionaryKey, Vec<&Expr>> =
|
||||||
|
FxHashMap::with_capacity_and_hasher(keys.len(), BuildHasherDefault::default());
|
||||||
|
|
||||||
|
// Detect duplicate keys.
|
||||||
|
for (i, key) in keys.iter().enumerate() {
|
||||||
|
if let Some(key) = into_dictionary_key(key, checker.style) {
|
||||||
|
if let Some(seen_values) = seen.get_mut(&key) {
|
||||||
|
match key {
|
||||||
|
DictionaryKey::Constant(key) => {
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::F601) {
|
||||||
|
let repeated_value =
|
||||||
|
seen_values.iter().any(|value| cmp::expr(value, &values[i]));
|
||||||
|
let mut check = Check::new(
|
||||||
|
violations::MultiValueRepeatedKeyLiteral(key, repeated_value),
|
||||||
|
Range::from_located(&keys[i]),
|
||||||
|
);
|
||||||
|
if repeated_value {
|
||||||
|
if checker.patch(&CheckCode::F601) {
|
||||||
|
check.amend(Fix::deletion(
|
||||||
|
values[i - 1].end_location.unwrap(),
|
||||||
|
values[i].end_location.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
seen_values.push(&values[i]);
|
||||||
|
}
|
||||||
|
checker.checks.push(check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DictionaryKey::Variable(key) => {
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::F602) {
|
||||||
|
let repeated_value =
|
||||||
|
seen_values.iter().any(|value| cmp::expr(value, &values[i]));
|
||||||
|
let mut check = Check::new(
|
||||||
|
violations::MultiValueRepeatedKeyVariable(
|
||||||
|
key.to_string(),
|
||||||
|
repeated_value,
|
||||||
|
),
|
||||||
|
Range::from_located(&keys[i]),
|
||||||
|
);
|
||||||
|
if repeated_value {
|
||||||
|
if checker.patch(&CheckCode::F602) {
|
||||||
|
check.amend(Fix::deletion(
|
||||||
|
values[i - 1].end_location.unwrap(),
|
||||||
|
values[i].end_location.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
seen_values.push(&values[i]);
|
||||||
|
}
|
||||||
|
checker.checks.push(check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
seen.insert(key, vec![&values[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@ source: src/pyflakes/mod.rs
|
||||||
expression: checks
|
expression: checks
|
||||||
---
|
---
|
||||||
- kind:
|
- kind:
|
||||||
MultiValueRepeatedKeyLiteral: ~
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
location:
|
location:
|
||||||
row: 3
|
row: 3
|
||||||
column: 4
|
column: 4
|
||||||
|
|
@ -13,7 +15,9 @@ expression: checks
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
- kind:
|
- kind:
|
||||||
MultiValueRepeatedKeyLiteral: ~
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "1"
|
||||||
|
- false
|
||||||
location:
|
location:
|
||||||
row: 9
|
row: 9
|
||||||
column: 4
|
column: 4
|
||||||
|
|
@ -23,7 +27,9 @@ expression: checks
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
- kind:
|
- kind:
|
||||||
MultiValueRepeatedKeyLiteral: ~
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "b\"123\""
|
||||||
|
- false
|
||||||
location:
|
location:
|
||||||
row: 11
|
row: 11
|
||||||
column: 4
|
column: 4
|
||||||
|
|
@ -32,4 +38,238 @@ expression: checks
|
||||||
column: 10
|
column: 10
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 16
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 16
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 17
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 17
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 7
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 17
|
||||||
|
column: 10
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 10
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 24
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 24
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 25
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 25
|
||||||
|
column: 7
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 24
|
||||||
|
column: 10
|
||||||
|
end_location:
|
||||||
|
row: 25
|
||||||
|
column: 10
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 26
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 26
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 31
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 31
|
||||||
|
column: 7
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 10
|
||||||
|
end_location:
|
||||||
|
row: 31
|
||||||
|
column: 10
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 32
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 32
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 33
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 33
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 34
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 34
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 41
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 41
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 43
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 43
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 45
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 7
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 10
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 49
|
||||||
|
column: 13
|
||||||
|
end_location:
|
||||||
|
row: 49
|
||||||
|
column: 16
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 49
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 49
|
||||||
|
column: 19
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyLiteral:
|
||||||
|
- "\"a\""
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 50
|
||||||
|
column: 21
|
||||||
|
end_location:
|
||||||
|
row: 50
|
||||||
|
column: 24
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 50
|
||||||
|
column: 19
|
||||||
|
end_location:
|
||||||
|
row: 50
|
||||||
|
column: 27
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ source: src/pyflakes/mod.rs
|
||||||
expression: checks
|
expression: checks
|
||||||
---
|
---
|
||||||
- kind:
|
- kind:
|
||||||
MultiValueRepeatedKeyVariable: a
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
location:
|
location:
|
||||||
row: 5
|
row: 5
|
||||||
column: 4
|
column: 4
|
||||||
|
|
@ -12,4 +14,250 @@ expression: checks
|
||||||
column: 5
|
column: 5
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 11
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 11
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 12
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 12
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 13
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 12
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 8
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 19
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 19
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 20
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 20
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 19
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 20
|
||||||
|
column: 8
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 21
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 21
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 26
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 26
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 25
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 26
|
||||||
|
column: 8
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 27
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 27
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 28
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 29
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 35
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 35
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 34
|
||||||
|
column: 10
|
||||||
|
end_location:
|
||||||
|
row: 35
|
||||||
|
column: 8
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 37
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 37
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 39
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 39
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- false
|
||||||
|
location:
|
||||||
|
row: 41
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 41
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 12
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 44
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 44
|
||||||
|
column: 15
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
MultiValueRepeatedKeyVariable:
|
||||||
|
- a
|
||||||
|
- true
|
||||||
|
location:
|
||||||
|
row: 45
|
||||||
|
column: 17
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 18
|
||||||
|
fix:
|
||||||
|
content: ""
|
||||||
|
location:
|
||||||
|
row: 45
|
||||||
|
column: 15
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 21
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -651,29 +651,50 @@ impl AlwaysAutofixableViolation for FStringMissingPlaceholders {
|
||||||
}
|
}
|
||||||
|
|
||||||
define_violation!(
|
define_violation!(
|
||||||
pub struct MultiValueRepeatedKeyLiteral;
|
pub struct MultiValueRepeatedKeyLiteral(pub String, pub bool);
|
||||||
);
|
);
|
||||||
impl Violation for MultiValueRepeatedKeyLiteral {
|
impl Violation for MultiValueRepeatedKeyLiteral {
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
"Dictionary key literal repeated".to_string()
|
let MultiValueRepeatedKeyLiteral(name, ..) = self;
|
||||||
|
format!("Dictionary key literal `{name}` repeated")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
|
let MultiValueRepeatedKeyLiteral(.., repeated_value) = self;
|
||||||
|
if *repeated_value {
|
||||||
|
Some(|MultiValueRepeatedKeyLiteral(name, ..)| {
|
||||||
|
format!("Remove repeated key literal `{name}`")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder() -> Self {
|
fn placeholder() -> Self {
|
||||||
MultiValueRepeatedKeyLiteral
|
MultiValueRepeatedKeyLiteral("...".to_string(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_violation!(
|
define_violation!(
|
||||||
pub struct MultiValueRepeatedKeyVariable(pub String);
|
pub struct MultiValueRepeatedKeyVariable(pub String, pub bool);
|
||||||
);
|
);
|
||||||
impl Violation for MultiValueRepeatedKeyVariable {
|
impl Violation for MultiValueRepeatedKeyVariable {
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let MultiValueRepeatedKeyVariable(name) = self;
|
let MultiValueRepeatedKeyVariable(name, ..) = self;
|
||||||
format!("Dictionary key `{name}` repeated")
|
format!("Dictionary key `{name}` repeated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
|
let MultiValueRepeatedKeyVariable(.., repeated_value) = self;
|
||||||
|
if *repeated_value {
|
||||||
|
Some(|MultiValueRepeatedKeyVariable(name, ..)| format!("Remove repeated key `{name}`"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn placeholder() -> Self {
|
fn placeholder() -> Self {
|
||||||
MultiValueRepeatedKeyVariable("...".to_string())
|
MultiValueRepeatedKeyVariable("...".to_string(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue