Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0152814a00 | ||
|
|
0b3fab256b | ||
|
|
212ce4d331 | ||
|
|
491b1e4968 | ||
|
|
8b01b53d89 | ||
|
|
f9a5867d3e | ||
|
|
4149627f19 | ||
|
|
7d24146df7 | ||
|
|
1c6ef3666c | ||
|
|
16d933fcf5 | ||
|
|
a9cc56b2ac | ||
|
|
4de6c26ff9 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -4,7 +4,7 @@ Thank you for taking the time to report an issue! We're glad to have you involve
|
||||
If you're filing a bug report, please consider including the following information:
|
||||
|
||||
- A minimal code snippet that reproduces the bug.
|
||||
- The command you invoked (e.g., `ruff /path/to/file.py --fix`).
|
||||
- The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
||||
- The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
||||
- The current Ruff version (`ruff --version`).
|
||||
-->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.214
|
||||
rev: v0.0.215
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -735,7 +735,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.214-dev.0"
|
||||
version = "0.0.215-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1873,7 +1873,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1941,7 +1941,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1961,7 +1961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -29,7 +29,7 @@ bitflags = { version = "1.3.2" }
|
||||
cachedir = { version = "0.3.0" }
|
||||
cfg-if = { version = "1.0.0" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
clap = { version = "4.0.1", features = ["derive", "env"] }
|
||||
clap_complete_command = { version = "0.4.0" }
|
||||
colored = { version = "2.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
@@ -51,7 +51,7 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.214", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.215", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "d532160333ffeb6dbeca2c2728c2391cd1e53b7f" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "d532160333ffeb6dbeca2c2728c2391cd1e53b7f" }
|
||||
|
||||
12
README.md
12
README.md
@@ -180,7 +180,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.214'
|
||||
rev: 'v0.0.215'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
@@ -341,6 +341,8 @@ Options:
|
||||
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
--isolated
|
||||
Ignore all configuration files
|
||||
--select <SELECT>
|
||||
Comma-separated list of error codes to enable (or ALL, to enable all checks)
|
||||
--extend-select <EXTEND_SELECT>
|
||||
@@ -360,11 +362,11 @@ Options:
|
||||
--per-file-ignores <PER_FILE_IGNORES>
|
||||
List of mappings from file pattern to code to exclude
|
||||
--format <FORMAT>
|
||||
Output serialization format for error messages [possible values: text, json, junit, grouped, github, gitlab]
|
||||
Output serialization format for error messages [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab]
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
--cache-dir <CACHE_DIR>
|
||||
Path to the cache directory
|
||||
Path to the cache directory [env: RUFF_CACHE_DIR=]
|
||||
--show-source
|
||||
Show violations with source code
|
||||
--respect-gitignore
|
||||
@@ -551,8 +553,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): ... | |
|
||||
| F525 | StringDotFormatMixingAutomatic | '...'.format(...) mixes automatic and manual numbering | |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders | 🛠 |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal `...` repeated | 🛠 |
|
||||
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | 🛠 |
|
||||
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
|
||||
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
|
||||
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.214-dev.0"
|
||||
version = "0.0.215-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
|
||||
28
resources/test/fixtures/isort/skip.py
vendored
28
resources/test/fixtures/isort/skip.py
vendored
@@ -1,10 +1,20 @@
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
def f():
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
def f():
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
|
||||
def f():
|
||||
import sys
|
||||
import os # isort:skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
38
resources/test/fixtures/pyflakes/F601.py
vendored
38
resources/test/fixtures/pyflakes/F601.py
vendored
@@ -10,3 +10,41 @@ x = {
|
||||
b"123": 1,
|
||||
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}
|
||||
|
||||
38
resources/test/fixtures/pyflakes/F602.py
vendored
38
resources/test/fixtures/pyflakes/F602.py
vendored
@@ -5,3 +5,41 @@ x = {
|
||||
a: 2,
|
||||
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}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.214"
|
||||
version = "0.0.215"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
524
src/ast/comparable.rs
Normal file
524
src/ast/comparable.rs
Normal file
@@ -0,0 +1,524 @@
|
||||
//! An equivalent object hierarchy to the `Expr` hierarchy, but with the ability
|
||||
//! to compare expressions for equality (via `Eq` and `Hash`).
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_ast::{
|
||||
Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Expr, ExprContext, ExprKind, Keyword,
|
||||
Operator, Unaryop,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableExprContext {
|
||||
Load,
|
||||
Store,
|
||||
Del,
|
||||
}
|
||||
|
||||
impl From<&ExprContext> for ComparableExprContext {
|
||||
fn from(ctx: &ExprContext) -> Self {
|
||||
match ctx {
|
||||
ExprContext::Load => Self::Load,
|
||||
ExprContext::Store => Self::Store,
|
||||
ExprContext::Del => Self::Del,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableBoolop {
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
impl From<&Boolop> for ComparableBoolop {
|
||||
fn from(op: &Boolop) -> Self {
|
||||
match op {
|
||||
Boolop::And => Self::And,
|
||||
Boolop::Or => Self::Or,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableOperator {
|
||||
Add,
|
||||
Sub,
|
||||
Mult,
|
||||
MatMult,
|
||||
Div,
|
||||
Mod,
|
||||
Pow,
|
||||
LShift,
|
||||
RShift,
|
||||
BitOr,
|
||||
BitXor,
|
||||
BitAnd,
|
||||
FloorDiv,
|
||||
}
|
||||
|
||||
impl From<&Operator> for ComparableOperator {
|
||||
fn from(op: &Operator) -> Self {
|
||||
match op {
|
||||
Operator::Add => Self::Add,
|
||||
Operator::Sub => Self::Sub,
|
||||
Operator::Mult => Self::Mult,
|
||||
Operator::MatMult => Self::MatMult,
|
||||
Operator::Div => Self::Div,
|
||||
Operator::Mod => Self::Mod,
|
||||
Operator::Pow => Self::Pow,
|
||||
Operator::LShift => Self::LShift,
|
||||
Operator::RShift => Self::RShift,
|
||||
Operator::BitOr => Self::BitOr,
|
||||
Operator::BitXor => Self::BitXor,
|
||||
Operator::BitAnd => Self::BitAnd,
|
||||
Operator::FloorDiv => Self::FloorDiv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableUnaryop {
|
||||
Invert,
|
||||
Not,
|
||||
UAdd,
|
||||
USub,
|
||||
}
|
||||
|
||||
impl From<&Unaryop> for ComparableUnaryop {
|
||||
fn from(op: &Unaryop) -> Self {
|
||||
match op {
|
||||
Unaryop::Invert => Self::Invert,
|
||||
Unaryop::Not => Self::Not,
|
||||
Unaryop::UAdd => Self::UAdd,
|
||||
Unaryop::USub => Self::USub,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
Lt,
|
||||
LtE,
|
||||
Gt,
|
||||
GtE,
|
||||
Is,
|
||||
IsNot,
|
||||
In,
|
||||
NotIn,
|
||||
}
|
||||
|
||||
impl From<&Cmpop> for ComparableCmpop {
|
||||
fn from(op: &Cmpop) -> Self {
|
||||
match op {
|
||||
Cmpop::Eq => Self::Eq,
|
||||
Cmpop::NotEq => Self::NotEq,
|
||||
Cmpop::Lt => Self::Lt,
|
||||
Cmpop::LtE => Self::LtE,
|
||||
Cmpop::Gt => Self::Gt,
|
||||
Cmpop::GtE => Self::GtE,
|
||||
Cmpop::Is => Self::Is,
|
||||
Cmpop::IsNot => Self::IsNot,
|
||||
Cmpop::In => Self::In,
|
||||
Cmpop::NotIn => Self::NotIn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableConstant<'a> {
|
||||
None,
|
||||
Bool(&'a bool),
|
||||
Str(&'a str),
|
||||
Bytes(&'a [u8]),
|
||||
Int(&'a BigInt),
|
||||
Tuple(Vec<ComparableConstant<'a>>),
|
||||
Float(u64),
|
||||
Complex { real: u64, imag: u64 },
|
||||
Ellipsis,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Constant> for ComparableConstant<'a> {
|
||||
fn from(constant: &'a Constant) -> Self {
|
||||
match constant {
|
||||
Constant::None => Self::None,
|
||||
Constant::Bool(value) => Self::Bool(value),
|
||||
Constant::Str(value) => Self::Str(value),
|
||||
Constant::Bytes(value) => Self::Bytes(value),
|
||||
Constant::Int(value) => Self::Int(value),
|
||||
Constant::Tuple(value) => {
|
||||
Self::Tuple(value.iter().map(std::convert::Into::into).collect())
|
||||
}
|
||||
Constant::Float(value) => Self::Float(value.to_bits()),
|
||||
Constant::Complex { real, imag } => Self::Complex {
|
||||
real: real.to_bits(),
|
||||
imag: imag.to_bits(),
|
||||
},
|
||||
Constant::Ellipsis => Self::Ellipsis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableArguments<'a> {
|
||||
pub posonlyargs: Vec<ComparableArg<'a>>,
|
||||
pub args: Vec<ComparableArg<'a>>,
|
||||
pub vararg: Option<ComparableArg<'a>>,
|
||||
pub kwonlyargs: Vec<ComparableArg<'a>>,
|
||||
pub kw_defaults: Vec<ComparableExpr<'a>>,
|
||||
pub kwarg: Option<ComparableArg<'a>>,
|
||||
pub defaults: Vec<ComparableExpr<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arguments> for ComparableArguments<'a> {
|
||||
fn from(arguments: &'a Arguments) -> Self {
|
||||
Self {
|
||||
posonlyargs: arguments
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
args: arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
vararg: arguments.vararg.as_ref().map(std::convert::Into::into),
|
||||
kwonlyargs: arguments
|
||||
.kwonlyargs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
kw_defaults: arguments
|
||||
.kw_defaults
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
kwarg: arguments.vararg.as_ref().map(std::convert::Into::into),
|
||||
defaults: arguments
|
||||
.defaults
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<Arg>> for ComparableArg<'a> {
|
||||
fn from(arg: &'a Box<Arg>) -> Self {
|
||||
(&**arg).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableArg<'a> {
|
||||
pub arg: &'a str,
|
||||
pub annotation: Option<Box<ComparableExpr<'a>>>,
|
||||
pub type_comment: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arg> for ComparableArg<'a> {
|
||||
fn from(arg: &'a Arg) -> Self {
|
||||
Self {
|
||||
arg: &arg.node.arg,
|
||||
annotation: arg.node.annotation.as_ref().map(std::convert::Into::into),
|
||||
type_comment: arg.node.type_comment.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableKeyword<'a> {
|
||||
pub arg: Option<&'a str>,
|
||||
pub value: ComparableExpr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Keyword> for ComparableKeyword<'a> {
|
||||
fn from(keyword: &'a Keyword) -> Self {
|
||||
Self {
|
||||
arg: keyword.node.arg.as_deref(),
|
||||
value: (&keyword.node.value).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableComprehension<'a> {
|
||||
pub target: ComparableExpr<'a>,
|
||||
pub iter: ComparableExpr<'a>,
|
||||
pub ifs: Vec<ComparableExpr<'a>>,
|
||||
pub is_async: &'a usize,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Comprehension> for ComparableComprehension<'a> {
|
||||
fn from(comprehension: &'a Comprehension) -> Self {
|
||||
Self {
|
||||
target: (&comprehension.target).into(),
|
||||
iter: (&comprehension.iter).into(),
|
||||
ifs: comprehension
|
||||
.ifs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
is_async: &comprehension.is_async,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableExpr<'a> {
|
||||
BoolOp {
|
||||
op: ComparableBoolop,
|
||||
values: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
NamedExpr {
|
||||
target: Box<ComparableExpr<'a>>,
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
BinOp {
|
||||
left: Box<ComparableExpr<'a>>,
|
||||
op: ComparableOperator,
|
||||
right: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
UnaryOp {
|
||||
op: ComparableUnaryop,
|
||||
operand: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
Lambda {
|
||||
args: ComparableArguments<'a>,
|
||||
body: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
IfExp {
|
||||
test: Box<ComparableExpr<'a>>,
|
||||
body: Box<ComparableExpr<'a>>,
|
||||
orelse: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
Dict {
|
||||
keys: Vec<ComparableExpr<'a>>,
|
||||
values: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
Set {
|
||||
elts: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
ListComp {
|
||||
elt: Box<ComparableExpr<'a>>,
|
||||
generators: Vec<ComparableComprehension<'a>>,
|
||||
},
|
||||
SetComp {
|
||||
elt: Box<ComparableExpr<'a>>,
|
||||
generators: Vec<ComparableComprehension<'a>>,
|
||||
},
|
||||
DictComp {
|
||||
key: Box<ComparableExpr<'a>>,
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
generators: Vec<ComparableComprehension<'a>>,
|
||||
},
|
||||
GeneratorExp {
|
||||
elt: Box<ComparableExpr<'a>>,
|
||||
generators: Vec<ComparableComprehension<'a>>,
|
||||
},
|
||||
Await {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
Yield {
|
||||
value: Option<Box<ComparableExpr<'a>>>,
|
||||
},
|
||||
YieldFrom {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
},
|
||||
Compare {
|
||||
left: Box<ComparableExpr<'a>>,
|
||||
ops: Vec<ComparableCmpop>,
|
||||
comparators: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
Call {
|
||||
func: Box<ComparableExpr<'a>>,
|
||||
args: Vec<ComparableExpr<'a>>,
|
||||
keywords: Vec<ComparableKeyword<'a>>,
|
||||
},
|
||||
FormattedValue {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
conversion: &'a usize,
|
||||
format_spec: Option<Box<ComparableExpr<'a>>>,
|
||||
},
|
||||
JoinedStr {
|
||||
values: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
Constant {
|
||||
value: ComparableConstant<'a>,
|
||||
kind: Option<&'a str>,
|
||||
},
|
||||
Attribute {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
attr: &'a str,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
Subscript {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
slice: Box<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
Starred {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
Name {
|
||||
id: &'a str,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
List {
|
||||
elts: Vec<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
Tuple {
|
||||
elts: Vec<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
},
|
||||
Slice {
|
||||
lower: Option<Box<ComparableExpr<'a>>>,
|
||||
upper: Option<Box<ComparableExpr<'a>>>,
|
||||
step: Option<Box<ComparableExpr<'a>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<Expr>> for Box<ComparableExpr<'a>> {
|
||||
fn from(expr: &'a Box<Expr>) -> Self {
|
||||
Box::new((&**expr).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
fn from(expr: &'a Expr) -> Self {
|
||||
match &expr.node {
|
||||
ExprKind::BoolOp { op, values } => Self::BoolOp {
|
||||
op: op.into(),
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::NamedExpr { target, value } => Self::NamedExpr {
|
||||
target: target.into(),
|
||||
value: value.into(),
|
||||
},
|
||||
ExprKind::BinOp { left, op, right } => Self::BinOp {
|
||||
left: left.into(),
|
||||
op: op.into(),
|
||||
right: right.into(),
|
||||
},
|
||||
ExprKind::UnaryOp { op, operand } => Self::UnaryOp {
|
||||
op: op.into(),
|
||||
operand: operand.into(),
|
||||
},
|
||||
ExprKind::Lambda { args, body } => Self::Lambda {
|
||||
args: (&**args).into(),
|
||||
body: body.into(),
|
||||
},
|
||||
ExprKind::IfExp { test, body, orelse } => Self::IfExp {
|
||||
test: test.into(),
|
||||
body: body.into(),
|
||||
orelse: orelse.into(),
|
||||
},
|
||||
ExprKind::Dict { keys, values } => Self::Dict {
|
||||
keys: keys.iter().map(std::convert::Into::into).collect(),
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::Set { elts } => Self::Set {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::ListComp { elt, generators } => Self::ListComp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::SetComp { elt, generators } => Self::SetComp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
value,
|
||||
generators,
|
||||
} => Self::DictComp {
|
||||
key: key.into(),
|
||||
value: value.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::GeneratorExp { elt, generators } => Self::GeneratorExp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::Await { value } => Self::Await {
|
||||
value: value.into(),
|
||||
},
|
||||
ExprKind::Yield { value } => Self::Yield {
|
||||
value: value.as_ref().map(std::convert::Into::into),
|
||||
},
|
||||
ExprKind::YieldFrom { value } => Self::YieldFrom {
|
||||
value: value.into(),
|
||||
},
|
||||
ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} => Self::Compare {
|
||||
left: left.into(),
|
||||
ops: ops.iter().map(std::convert::Into::into).collect(),
|
||||
comparators: comparators.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => Self::Call {
|
||||
func: func.into(),
|
||||
args: args.iter().map(std::convert::Into::into).collect(),
|
||||
keywords: keywords.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::FormattedValue {
|
||||
value,
|
||||
conversion,
|
||||
format_spec,
|
||||
} => Self::FormattedValue {
|
||||
value: value.into(),
|
||||
conversion,
|
||||
format_spec: format_spec.as_ref().map(std::convert::Into::into),
|
||||
},
|
||||
ExprKind::JoinedStr { values } => Self::JoinedStr {
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
},
|
||||
ExprKind::Constant { value, kind } => Self::Constant {
|
||||
value: value.into(),
|
||||
kind: kind.as_ref().map(String::as_str),
|
||||
},
|
||||
ExprKind::Attribute { value, attr, ctx } => Self::Attribute {
|
||||
value: value.into(),
|
||||
attr,
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Subscript { value, slice, ctx } => Self::Subscript {
|
||||
value: value.into(),
|
||||
slice: slice.into(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Starred { value, ctx } => Self::Starred {
|
||||
value: value.into(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Name { id, ctx } => Self::Name {
|
||||
id,
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::List { elts, ctx } => Self::List {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Tuple { elts, ctx } => Self::Tuple {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Slice { lower, upper, step } => Self::Slice {
|
||||
lower: lower.as_ref().map(std::convert::Into::into),
|
||||
upper: upper.as_ref().map(std::convert::Into::into),
|
||||
step: step.as_ref().map(std::convert::Into::into),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod branch_detection;
|
||||
pub mod cast;
|
||||
pub mod comparable;
|
||||
pub mod function_type;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
|
||||
18
src/cache.rs
18
src/cache.rs
@@ -1,6 +1,5 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs;
|
||||
use std::fs::{create_dir_all, File, Metadata};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -8,16 +7,15 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
use filetime::FileTime;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::message::Message;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
pub const CACHE_DIR_NAME: &str = ".ruff_cache";
|
||||
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static CACHE_DIR: Lazy<Option<String>> = Lazy::new(|| std::env::var("RUFF_CACHE_DIR").ok());
|
||||
pub const DEFAULT_CACHE_DIR_NAME: &str = ".ruff_cache";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheMetadata {
|
||||
@@ -39,9 +37,7 @@ struct CheckResult {
|
||||
/// Return the cache directory for a given project root. Defers to the
|
||||
/// `RUFF_CACHE_DIR` environment variable, if set.
|
||||
pub fn cache_dir(project_root: &Path) -> PathBuf {
|
||||
CACHE_DIR
|
||||
.as_ref()
|
||||
.map_or_else(|| project_root.join(DEFAULT_CACHE_DIR_NAME), PathBuf::from)
|
||||
project_root.join(CACHE_DIR_NAME)
|
||||
}
|
||||
|
||||
fn content_dir() -> &'static Path {
|
||||
@@ -60,7 +56,7 @@ fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: flags::Autof
|
||||
/// Initialize the cache at the specified `Path`.
|
||||
pub fn init(path: &Path) -> Result<()> {
|
||||
// Create the cache directories.
|
||||
create_dir_all(path.join(content_dir()))?;
|
||||
fs::create_dir_all(path.join(content_dir()))?;
|
||||
|
||||
// Add the CACHEDIR.TAG.
|
||||
if !cachedir::is_tagged(path)? {
|
||||
@@ -70,7 +66,7 @@ pub fn init(path: &Path) -> Result<()> {
|
||||
// Add the .gitignore.
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
let mut file = File::create(gitignore_path)?;
|
||||
let mut file = fs::File::create(gitignore_path)?;
|
||||
file.write_all(b"*")?;
|
||||
}
|
||||
|
||||
@@ -91,7 +87,7 @@ fn read_sync(cache_dir: &Path, key: u64) -> Result<Vec<u8>, std::io::Error> {
|
||||
/// Get a value from the cache.
|
||||
pub fn get<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &Metadata,
|
||||
metadata: &fs::Metadata,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Vec<Message>> {
|
||||
@@ -115,7 +111,7 @@ pub fn get<P: AsRef<Path>>(
|
||||
/// Set a value in the cache.
|
||||
pub fn set<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &Metadata,
|
||||
metadata: &fs::Metadata,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
messages: &[Message],
|
||||
|
||||
@@ -2385,15 +2385,11 @@ where
|
||||
));
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, .. } => {
|
||||
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
|
||||
let check_repeated_variables = self.settings.enabled.contains(&CheckCode::F602);
|
||||
if check_repeated_literals || check_repeated_variables {
|
||||
self.checks.extend(pyflakes::checks::repeated_keys(
|
||||
keys,
|
||||
check_repeated_literals,
|
||||
check_repeated_variables,
|
||||
));
|
||||
ExprKind::Dict { keys, values } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F601)
|
||||
|| self.settings.enabled.contains(&CheckCode::F602)
|
||||
{
|
||||
pyflakes::plugins::repeated_keys(self, keys, values);
|
||||
}
|
||||
}
|
||||
ExprKind::Yield { .. } => {
|
||||
|
||||
11
src/cli.rs
11
src/cli.rs
@@ -20,7 +20,7 @@ pub struct Cli {
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||
/// configuration.
|
||||
#[arg(long)]
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
@@ -56,6 +56,9 @@ pub struct Cli {
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config")]
|
||||
pub isolated: bool,
|
||||
/// Comma-separated list of error codes to enable (or ALL, to enable all
|
||||
/// checks).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
@@ -90,13 +93,13 @@ pub struct Cli {
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum)]
|
||||
#[arg(long, value_enum, env = "RUFF_FORMAT")]
|
||||
pub format: Option<SerializationFormat>,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "RUFF_CACHE_DIR")]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
/// Show violations with source code.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
@@ -240,6 +243,7 @@ impl Cli {
|
||||
explain: self.explain,
|
||||
files: self.files,
|
||||
generate_shell_completion: self.generate_shell_completion,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
quiet: self.quiet,
|
||||
show_files: self.show_files,
|
||||
@@ -301,6 +305,7 @@ pub struct Arguments {
|
||||
pub explain: Option<CheckCode>,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
pub isolated: bool,
|
||||
pub no_cache: bool,
|
||||
pub quiet: bool,
|
||||
pub show_files: bool,
|
||||
|
||||
@@ -16,7 +16,7 @@ use serde::Serialize;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::cache::DEFAULT_CACHE_DIR_NAME;
|
||||
use crate::cache::CACHE_DIR_NAME;
|
||||
use crate::cli::Overrides;
|
||||
use crate::iterators::par_iter;
|
||||
use crate::linter::{add_noqa_to_path, lint_path, lint_stdin, Diagnostics};
|
||||
@@ -340,10 +340,10 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
pub fn clean(level: &LogLevel) -> Result<()> {
|
||||
for entry in WalkDir::new(&*path_dedot::CWD)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.file_type().is_dir())
|
||||
{
|
||||
let cache = entry.path().join(DEFAULT_CACHE_DIR_NAME);
|
||||
let cache = entry.path().join(CACHE_DIR_NAME);
|
||||
if cache.is_dir() {
|
||||
if level >= &LogLevel::Default {
|
||||
eprintln!("Removing cache at: {}", fs::relativize_path(&cache).bold());
|
||||
|
||||
@@ -104,10 +104,13 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
|
||||
continue;
|
||||
};
|
||||
|
||||
// `isort` allows for `# isort: skip` and `# isort: skip_file` to include or
|
||||
// omit a space after the colon. The remaining action comments are
|
||||
// required to include the space, and must appear on their own lines.
|
||||
let comment_text = comment_text.trim_end();
|
||||
if comment_text == "# isort: split" {
|
||||
splits.push(start.row());
|
||||
} else if comment_text == "# isort: skip_file" {
|
||||
} else if comment_text == "# isort: skip_file" || comment_text == "# isort:skip_file" {
|
||||
skip_file = true;
|
||||
} else if off.is_some() {
|
||||
if comment_text == "# isort: on" {
|
||||
@@ -119,7 +122,7 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
|
||||
off = None;
|
||||
}
|
||||
} else {
|
||||
if comment_text.contains("isort: skip") {
|
||||
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
|
||||
exclusions.insert(start.row());
|
||||
} else if comment_text == "# isort: off" {
|
||||
off = Some(start);
|
||||
|
||||
@@ -26,10 +26,10 @@ pub enum UnittestAssert {
|
||||
ItemsEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
ListEqual,
|
||||
MultiLineEqual,
|
||||
NotAlmostEqual,
|
||||
NotAlmostEquals,
|
||||
NotContains,
|
||||
NotEqual,
|
||||
NotEquals,
|
||||
NotIn,
|
||||
@@ -41,8 +41,10 @@ pub enum UnittestAssert {
|
||||
RaisesRegexp,
|
||||
Regex,
|
||||
RegexpMatches,
|
||||
SequenceEqual,
|
||||
SetEqual,
|
||||
True,
|
||||
TupleEqual,
|
||||
Underscore,
|
||||
}
|
||||
|
||||
@@ -66,10 +68,10 @@ impl std::fmt::Display for UnittestAssert {
|
||||
UnittestAssert::ItemsEqual => write!(f, "assertItemsEqual"),
|
||||
UnittestAssert::Less => write!(f, "assertLess"),
|
||||
UnittestAssert::LessEqual => write!(f, "assertLessEqual"),
|
||||
UnittestAssert::ListEqual => write!(f, "assertListEqual"),
|
||||
UnittestAssert::MultiLineEqual => write!(f, "assertMultiLineEqual"),
|
||||
UnittestAssert::NotAlmostEqual => write!(f, "assertNotAlmostEqual"),
|
||||
UnittestAssert::NotAlmostEquals => write!(f, "assertNotAlmostEquals"),
|
||||
UnittestAssert::NotContains => write!(f, "assertNotContains"),
|
||||
UnittestAssert::NotEqual => write!(f, "assertNotEqual"),
|
||||
UnittestAssert::NotEquals => write!(f, "assertNotEquals"),
|
||||
UnittestAssert::NotIn => write!(f, "assertNotIn"),
|
||||
@@ -81,8 +83,10 @@ impl std::fmt::Display for UnittestAssert {
|
||||
UnittestAssert::RaisesRegexp => write!(f, "assertRaisesRegexp"),
|
||||
UnittestAssert::Regex => write!(f, "assertRegex"),
|
||||
UnittestAssert::RegexpMatches => write!(f, "assertRegexpMatches"),
|
||||
UnittestAssert::SequenceEqual => write!(f, "assertSequenceEqual"),
|
||||
UnittestAssert::SetEqual => write!(f, "assertSetEqual"),
|
||||
UnittestAssert::True => write!(f, "assertTrue"),
|
||||
UnittestAssert::TupleEqual => write!(f, "assertTupleEqual"),
|
||||
UnittestAssert::Underscore => write!(f, "assert_"),
|
||||
}
|
||||
}
|
||||
@@ -110,10 +114,10 @@ impl TryFrom<&str> for UnittestAssert {
|
||||
"assertItemsEqual" => Ok(UnittestAssert::ItemsEqual),
|
||||
"assertLess" => Ok(UnittestAssert::Less),
|
||||
"assertLessEqual" => Ok(UnittestAssert::LessEqual),
|
||||
"assertListEqual" => Ok(UnittestAssert::ListEqual),
|
||||
"assertMultiLineEqual" => Ok(UnittestAssert::MultiLineEqual),
|
||||
"assertNotAlmostEqual" => Ok(UnittestAssert::NotAlmostEqual),
|
||||
"assertNotAlmostEquals" => Ok(UnittestAssert::NotAlmostEquals),
|
||||
"assertNotContains" => Ok(UnittestAssert::NotContains),
|
||||
"assertNotEqual" => Ok(UnittestAssert::NotEqual),
|
||||
"assertNotEquals" => Ok(UnittestAssert::NotEquals),
|
||||
"assertNotIn" => Ok(UnittestAssert::NotIn),
|
||||
@@ -125,8 +129,10 @@ impl TryFrom<&str> for UnittestAssert {
|
||||
"assertRaisesRegexp" => Ok(UnittestAssert::RaisesRegexp),
|
||||
"assertRegex" => Ok(UnittestAssert::Regex),
|
||||
"assertRegexpMatches" => Ok(UnittestAssert::RegexpMatches),
|
||||
"assertSequenceEqual" => Ok(UnittestAssert::SequenceEqual),
|
||||
"assertSetEqual" => Ok(UnittestAssert::SetEqual),
|
||||
"assertTrue" => Ok(UnittestAssert::True),
|
||||
"assertTupleEqual" => Ok(UnittestAssert::TupleEqual),
|
||||
"assert_" => Ok(UnittestAssert::Underscore),
|
||||
_ => Err(format!("Unknown unittest assert method: {value}")),
|
||||
}
|
||||
@@ -190,10 +196,10 @@ impl UnittestAssert {
|
||||
UnittestAssert::ItemsEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::Less => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::LessEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::ListEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::MultiLineEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::NotAlmostEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::NotAlmostEquals => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::NotContains => Arguments::new(vec!["container", "member"], vec!["msg"]),
|
||||
UnittestAssert::NotEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::NotEquals => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::NotIn => Arguments::new(vec!["member", "container"], vec!["msg"]),
|
||||
@@ -205,8 +211,10 @@ impl UnittestAssert {
|
||||
UnittestAssert::RaisesRegexp => Arguments::new(vec!["exception", "regex"], vec!["msg"]),
|
||||
UnittestAssert::Regex => Arguments::new(vec!["text", "regex"], vec!["msg"]),
|
||||
UnittestAssert::RegexpMatches => Arguments::new(vec!["text", "regex"], vec!["msg"]),
|
||||
UnittestAssert::SetEqual => Arguments::new(vec!["set1", "set2"], vec!["msg"]),
|
||||
UnittestAssert::SequenceEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::SetEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::True => Arguments::new(vec!["expr"], vec!["msg"]),
|
||||
UnittestAssert::TupleEqual => Arguments::new(vec!["first", "second"], vec!["msg"]),
|
||||
UnittestAssert::Underscore => Arguments::new(vec!["expr"], vec!["msg"]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn twisted_arms_in_ifexpr(
|
||||
}
|
||||
|
||||
let mut check = Check::new(
|
||||
violations::NegateEqualOp(
|
||||
violations::IfExprWithTwistedArms(
|
||||
unparse_expr(body, checker.style),
|
||||
unparse_expr(orelse, checker.style),
|
||||
),
|
||||
|
||||
@@ -3,7 +3,7 @@ source: src/flake8_simplify/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NegateEqualOp:
|
||||
IfExprWithTwistedArms:
|
||||
- b
|
||||
- a
|
||||
location:
|
||||
@@ -12,10 +12,17 @@ expression: checks
|
||||
end_location:
|
||||
row: 1
|
||||
column: 21
|
||||
fix: ~
|
||||
fix:
|
||||
content: a if a else b
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 21
|
||||
parent: ~
|
||||
- kind:
|
||||
NegateEqualOp:
|
||||
IfExprWithTwistedArms:
|
||||
- b + c
|
||||
- a
|
||||
location:
|
||||
@@ -24,6 +31,13 @@ expression: checks
|
||||
end_location:
|
||||
row: 3
|
||||
column: 25
|
||||
fix: ~
|
||||
fix:
|
||||
content: a if a else b + c
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 25
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -5,35 +5,35 @@ expression: checks
|
||||
- kind:
|
||||
UnsortedImports: ~
|
||||
location:
|
||||
row: 7
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 14
|
||||
column: 0
|
||||
fix:
|
||||
content: "import sys\n\n"
|
||||
content: " import abc\n import collections\n"
|
||||
location:
|
||||
row: 7
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
row: 14
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
UnsortedImports: ~
|
||||
location:
|
||||
row: 9
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
row: 21
|
||||
column: 0
|
||||
fix:
|
||||
content: "import abc\nimport collections\n"
|
||||
content: " import abc\n import collections\n"
|
||||
location:
|
||||
row: 9
|
||||
row: 19
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
row: 21
|
||||
column: 0
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::sync::mpsc::channel;
|
||||
|
||||
use ::ruff::autofix::fixer;
|
||||
use ::ruff::cli::{extract_log_level, Cli, Overrides};
|
||||
use ::ruff::commands;
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::printer::{Printer, Violations};
|
||||
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
|
||||
@@ -14,22 +13,29 @@ use ::ruff::settings::types::SerializationFormat;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{commands, one_time_warning};
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use path_absolutize::path_dedot;
|
||||
use ruff::one_time_warning;
|
||||
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
fn resolve(
|
||||
isolated: bool,
|
||||
config: Option<&Path>,
|
||||
overrides: &Overrides,
|
||||
stdin_filename: Option<&Path>,
|
||||
) -> Result<PyprojectDiscovery> {
|
||||
if let Some(pyproject) = config {
|
||||
// First priority: the user specified a `pyproject.toml` file. Use that
|
||||
if isolated {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
let mut config = Configuration::default();
|
||||
config.apply(overrides.clone());
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = config {
|
||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
@@ -39,7 +45,7 @@ fn resolve(
|
||||
.as_ref()
|
||||
.unwrap_or(&path_dedot::CWD.as_path()),
|
||||
)? {
|
||||
// Second priority: find a `pyproject.toml` file in either an ancestor of
|
||||
// Third priority: find a `pyproject.toml` file in either an ancestor of
|
||||
// `stdin_filename` (if set) or the current working path all paths relative to
|
||||
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
|
||||
// the "closest" `pyproject.toml` file for every Python file later on,
|
||||
@@ -47,7 +53,7 @@ fn resolve(
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||
// Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
||||
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
||||
// these act as the "default" settings.)
|
||||
@@ -59,7 +65,6 @@ fn resolve(
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
let mut config = Configuration::default();
|
||||
// Apply command-line options that override defaults.
|
||||
config.apply(overrides.clone());
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
@@ -84,6 +89,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let pyproject_strategy = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
cli.stdin_filename.as_deref(),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::string::ToString;
|
||||
|
||||
use rustpython_parser::ast::{
|
||||
Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
||||
};
|
||||
use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::except_range;
|
||||
use crate::ast::types::{Binding, Range, Scope, ScopeKind};
|
||||
@@ -70,59 +68,6 @@ pub fn default_except_not_last(
|
||||
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
|
||||
pub fn starred_expressions(
|
||||
elts: &[Expr],
|
||||
|
||||
@@ -4,6 +4,7 @@ pub use if_tuple::if_tuple;
|
||||
pub use invalid_literal_comparisons::invalid_literal_comparison;
|
||||
pub use invalid_print_syntax::invalid_print_syntax;
|
||||
pub use raise_not_implemented::raise_not_implemented;
|
||||
pub use repeated_keys::repeated_keys;
|
||||
pub(crate) use strings::{
|
||||
percent_format_expected_mapping, percent_format_expected_sequence,
|
||||
percent_format_extra_named_arguments, percent_format_missing_arguments,
|
||||
@@ -21,6 +22,7 @@ mod if_tuple;
|
||||
mod invalid_literal_comparisons;
|
||||
mod invalid_print_syntax;
|
||||
mod raise_not_implemented;
|
||||
mod repeated_keys;
|
||||
mod strings;
|
||||
mod unused_annotation;
|
||||
mod unused_variable;
|
||||
|
||||
93
src/pyflakes/plugins/repeated_keys.rs
Normal file
93
src/pyflakes/plugins/repeated_keys.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::hash::{BuildHasherDefault, Hash};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::comparable::{ComparableConstant, ComparableExpr};
|
||||
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::violations;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
enum DictionaryKey<'a> {
|
||||
Constant(ComparableConstant<'a>),
|
||||
Variable(&'a str),
|
||||
}
|
||||
|
||||
fn into_dictionary_key(expr: &Expr) -> Option<DictionaryKey> {
|
||||
match &expr.node {
|
||||
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value.into())),
|
||||
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, FxHashSet<ComparableExpr>> =
|
||||
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) {
|
||||
if let Some(seen_values) = seen.get_mut(&key) {
|
||||
match key {
|
||||
DictionaryKey::Constant(..) => {
|
||||
if checker.settings.enabled.contains(&CheckCode::F601) {
|
||||
let comparable_value: ComparableExpr = (&values[i]).into();
|
||||
let is_duplicate_value = seen_values.contains(&comparable_value);
|
||||
let mut check = Check::new(
|
||||
violations::MultiValueRepeatedKeyLiteral(
|
||||
unparse_expr(&keys[i], checker.style),
|
||||
is_duplicate_value,
|
||||
),
|
||||
Range::from_located(&keys[i]),
|
||||
);
|
||||
if is_duplicate_value {
|
||||
if checker.patch(&CheckCode::F601) {
|
||||
check.amend(Fix::deletion(
|
||||
values[i - 1].end_location.unwrap(),
|
||||
values[i].end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seen_values.insert(comparable_value);
|
||||
}
|
||||
checker.checks.push(check);
|
||||
}
|
||||
}
|
||||
DictionaryKey::Variable(key) => {
|
||||
if checker.settings.enabled.contains(&CheckCode::F602) {
|
||||
let comparable_value: ComparableExpr = (&values[i]).into();
|
||||
let is_duplicate_value = seen_values.contains(&comparable_value);
|
||||
let mut check = Check::new(
|
||||
violations::MultiValueRepeatedKeyVariable(
|
||||
key.to_string(),
|
||||
is_duplicate_value,
|
||||
),
|
||||
Range::from_located(&keys[i]),
|
||||
);
|
||||
if is_duplicate_value {
|
||||
if checker.patch(&CheckCode::F602) {
|
||||
check.amend(Fix::deletion(
|
||||
values[i - 1].end_location.unwrap(),
|
||||
values[i].end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seen_values.insert(comparable_value);
|
||||
}
|
||||
checker.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seen.insert(key, FxHashSet::from_iter([(&values[i]).into()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
MultiValueRepeatedKeyLiteral: ~
|
||||
MultiValueRepeatedKeyLiteral:
|
||||
- "\"a\""
|
||||
- false
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
@@ -13,7 +15,9 @@ expression: checks
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultiValueRepeatedKeyLiteral: ~
|
||||
MultiValueRepeatedKeyLiteral:
|
||||
- "1"
|
||||
- false
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
@@ -23,7 +27,9 @@ expression: checks
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultiValueRepeatedKeyLiteral: ~
|
||||
MultiValueRepeatedKeyLiteral:
|
||||
- "b\"123\""
|
||||
- false
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
@@ -32,4 +38,238 @@ expression: checks
|
||||
column: 10
|
||||
fix: ~
|
||||
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
|
||||
---
|
||||
- kind:
|
||||
MultiValueRepeatedKeyVariable: a
|
||||
MultiValueRepeatedKeyVariable:
|
||||
- a
|
||||
- false
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
@@ -12,4 +14,250 @@ expression: checks
|
||||
column: 5
|
||||
fix: ~
|
||||
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: ~
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::error;
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Location, Stmt, StmtKind};
|
||||
use log::debug;
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::helpers::{create_expr, create_stmt, match_module_member, unparse_stmt};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::registry::Check;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::violations;
|
||||
|
||||
/// Return the typename, args, keywords and mother class
|
||||
/// Return the typename, args, keywords, and base class.
|
||||
fn match_named_tuple_assign<'a>(
|
||||
checker: &Checker,
|
||||
targets: &'a [Expr],
|
||||
value: &'a Expr,
|
||||
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a ExprKind)> {
|
||||
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> {
|
||||
let target = targets.get(0)?;
|
||||
let ExprKind::Name { id: typename, .. } = &target.node else {
|
||||
return None;
|
||||
@@ -39,43 +38,25 @@ fn match_named_tuple_assign<'a>(
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some((typename, args, keywords, &func.node))
|
||||
Some((typename, args, keywords, func))
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind::AnnAssign` representing the provided property
|
||||
/// definition.
|
||||
fn create_property_assignment_stmt(
|
||||
property: &str,
|
||||
annotation: &ExprKind,
|
||||
value: Option<&ExprKind>,
|
||||
annotation: &Expr,
|
||||
value: Option<&Expr>,
|
||||
) -> Stmt {
|
||||
Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::AnnAssign {
|
||||
target: Box::new(Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Name {
|
||||
id: property.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)),
|
||||
annotation: Box::new(Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
annotation.clone(),
|
||||
)),
|
||||
value: value.map(|v| {
|
||||
Box::new(Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
v.clone(),
|
||||
))
|
||||
}),
|
||||
simple: 1,
|
||||
},
|
||||
)
|
||||
create_stmt(StmtKind::AnnAssign {
|
||||
target: Box::new(create_expr(ExprKind::Name {
|
||||
id: property.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
})),
|
||||
annotation: Box::new(annotation.clone()),
|
||||
value: value.map(|value| Box::new(value.clone())),
|
||||
simple: 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Match the `defaults` keyword in a `NamedTuple(...)` call.
|
||||
@@ -105,7 +86,6 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||
let ExprKind::List { elts, .. } = &fields.node else {
|
||||
bail!("Expected argument to be `ExprKind::List`");
|
||||
};
|
||||
|
||||
let padded_defaults = if elts.len() >= defaults.len() {
|
||||
std::iter::repeat(None)
|
||||
.take(elts.len() - defaults.len())
|
||||
@@ -132,9 +112,7 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||
bail!("Invalid property name: {}", property)
|
||||
}
|
||||
Ok(create_property_assignment_stmt(
|
||||
property,
|
||||
&annotation.node,
|
||||
default.map(|d| &d.node),
|
||||
property, annotation, default,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
@@ -142,35 +120,26 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||
|
||||
/// Generate a `StmtKind:ClassDef` statement based on the provided body and
|
||||
/// keywords.
|
||||
fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &ExprKind) -> Stmt {
|
||||
Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::ClassDef {
|
||||
name: typename.to_string(),
|
||||
bases: vec![Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
base_class.clone(),
|
||||
)],
|
||||
keywords: vec![],
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
},
|
||||
)
|
||||
fn create_class_def_stmt(typename: &str, body: Vec<Stmt>, base_class: &Expr) -> Stmt {
|
||||
create_stmt(StmtKind::ClassDef {
|
||||
name: typename.to_string(),
|
||||
bases: vec![base_class.clone()],
|
||||
keywords: vec![],
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a `Fix` to convert a `NamedTuple` assignment to a class definition.
|
||||
fn convert_to_class(
|
||||
stmt: &Stmt,
|
||||
typename: &str,
|
||||
body: Vec<Stmt>,
|
||||
base_class: &ExprKind,
|
||||
base_class: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Fix {
|
||||
let mut generator: SourceCodeGenerator = stylist.into();
|
||||
generator.unparse_stmt(&create_class_def_stmt(typename, body, base_class));
|
||||
Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_stmt(&create_class_def_stmt(typename, body, base_class), stylist),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
)
|
||||
@@ -188,26 +157,25 @@ pub fn convert_named_tuple_functional_to_class(
|
||||
{
|
||||
return;
|
||||
};
|
||||
match match_defaults(keywords) {
|
||||
Ok(defaults) => match create_properties_from_args(args, defaults) {
|
||||
let mut check = Check::new(
|
||||
violations::ConvertNamedTupleFunctionalToClass(typename.to_string()),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
match match_defaults(keywords)
|
||||
.and_then(|defaults| create_properties_from_args(args, defaults))
|
||||
{
|
||||
Ok(properties) => {
|
||||
let mut check = Check::new(
|
||||
violations::ConvertNamedTupleFunctionalToClass(typename.to_string()),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
check.amend(convert_to_class(
|
||||
stmt,
|
||||
typename,
|
||||
properties,
|
||||
base_class,
|
||||
checker.style,
|
||||
));
|
||||
}
|
||||
checker.checks.push(check);
|
||||
check.amend(convert_to_class(
|
||||
stmt,
|
||||
typename,
|
||||
properties,
|
||||
base_class,
|
||||
checker.style,
|
||||
));
|
||||
}
|
||||
Err(err) => error!("Failed to create properties: {err}"),
|
||||
},
|
||||
Err(err) => error!("Failed to parse defaults: {err}"),
|
||||
Err(err) => debug!("Skipping ineligible `NamedTuple` \"{typename}\": {err}"),
|
||||
};
|
||||
}
|
||||
checker.checks.push(check);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use anyhow::{bail, Result};
|
||||
use log::error;
|
||||
use rustpython_ast::{
|
||||
Constant, Expr, ExprContext, ExprKind, Keyword, KeywordData, Location, Stmt, StmtKind,
|
||||
};
|
||||
use log::debug;
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::helpers::{create_expr, create_stmt, match_module_member, unparse_stmt};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::python::identifiers::IDENTIFIER_REGEX;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::registry::Check;
|
||||
use crate::source_code_generator::SourceCodeGenerator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
use crate::violations;
|
||||
|
||||
@@ -21,7 +18,7 @@ fn match_typed_dict_assign<'a>(
|
||||
checker: &Checker,
|
||||
targets: &'a [Expr],
|
||||
value: &'a Expr,
|
||||
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a ExprKind)> {
|
||||
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> {
|
||||
let target = targets.get(0)?;
|
||||
let ExprKind::Name { id: class_name, .. } = &target.node else {
|
||||
return None;
|
||||
@@ -42,38 +39,26 @@ fn match_typed_dict_assign<'a>(
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some((class_name, args, keywords, &func.node))
|
||||
Some((class_name, args, keywords, func))
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind::AnnAssign` representing the provided property
|
||||
/// definition.
|
||||
fn create_property_assignment_stmt(property: &str, annotation: &ExprKind) -> Stmt {
|
||||
Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::AnnAssign {
|
||||
target: Box::new(Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
ExprKind::Name {
|
||||
id: property.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)),
|
||||
annotation: Box::new(Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
annotation.clone(),
|
||||
)),
|
||||
value: None,
|
||||
simple: 1,
|
||||
},
|
||||
)
|
||||
create_stmt(StmtKind::AnnAssign {
|
||||
target: Box::new(create_expr(ExprKind::Name {
|
||||
id: property.to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
})),
|
||||
annotation: Box::new(create_expr(annotation.clone())),
|
||||
value: None,
|
||||
simple: 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind::Pass` statement.
|
||||
fn create_pass_stmt() -> Stmt {
|
||||
Stmt::new(Location::default(), Location::default(), StmtKind::Pass)
|
||||
create_stmt(StmtKind::Pass)
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind:ClassDef` statement based on the provided body,
|
||||
@@ -81,35 +66,23 @@ fn create_pass_stmt() -> Stmt {
|
||||
fn create_class_def_stmt(
|
||||
class_name: &str,
|
||||
body: Vec<Stmt>,
|
||||
total_keyword: Option<KeywordData>,
|
||||
base_class: &ExprKind,
|
||||
total_keyword: Option<&Keyword>,
|
||||
base_class: &Expr,
|
||||
) -> Stmt {
|
||||
let keywords = match total_keyword {
|
||||
Some(keyword) => vec![Keyword::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
keyword,
|
||||
)],
|
||||
Some(keyword) => vec![keyword.clone()],
|
||||
None => vec![],
|
||||
};
|
||||
Stmt::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
StmtKind::ClassDef {
|
||||
name: class_name.to_string(),
|
||||
bases: vec![Expr::new(
|
||||
Location::default(),
|
||||
Location::default(),
|
||||
base_class.clone(),
|
||||
)],
|
||||
keywords,
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
},
|
||||
)
|
||||
create_stmt(StmtKind::ClassDef {
|
||||
name: class_name.to_string(),
|
||||
bases: vec![base_class.clone()],
|
||||
keywords,
|
||||
body,
|
||||
decorator_list: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn get_properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
fn properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
keys.iter()
|
||||
.zip(values.iter())
|
||||
.map(|(key, value)| match &key.node {
|
||||
@@ -120,7 +93,7 @@ fn get_properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Ve
|
||||
if IDENTIFIER_REGEX.is_match(property) && !KWLIST.contains(&property.as_str()) {
|
||||
Ok(create_property_assignment_stmt(property, &value.node))
|
||||
} else {
|
||||
bail!("Invalid property name: {}", property)
|
||||
bail!("Property name is not valid identifier: {}", property)
|
||||
}
|
||||
}
|
||||
_ => bail!("Expected `key` to be `Constant::Str`"),
|
||||
@@ -128,18 +101,18 @@ fn get_properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Ve
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_properties_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
fn properties_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
let ExprKind::Name { id, .. } = &func.node else {
|
||||
bail!("Expected `func` to be `ExprKind::Name`")
|
||||
};
|
||||
if id != "dict" {
|
||||
bail!("Expected `id` to be `\"dict\"`")
|
||||
}
|
||||
get_properties_from_keywords(keywords)
|
||||
properties_from_keywords(keywords)
|
||||
}
|
||||
|
||||
// Deprecated in Python 3.11, removed in Python 3.13.
|
||||
fn get_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
fn properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
keywords
|
||||
.iter()
|
||||
.map(|keyword| {
|
||||
@@ -156,36 +129,40 @@ fn get_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
}
|
||||
|
||||
// The only way to have the `total` keyword is to use the args version, like:
|
||||
// (`TypedDict('name', {'a': int}, total=True)`)
|
||||
fn get_total_from_only_keyword(keywords: &[Keyword]) -> Option<&KeywordData> {
|
||||
// ```
|
||||
// TypedDict('name', {'a': int}, total=True)
|
||||
// ```
|
||||
fn match_total_from_only_keyword(keywords: &[Keyword]) -> Option<&Keyword> {
|
||||
let keyword = keywords.get(0)?;
|
||||
let arg = &keyword.node.arg.as_ref()?;
|
||||
match arg.as_str() {
|
||||
"total" => Some(&keyword.node),
|
||||
"total" => Some(keyword),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_properties_and_total(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) -> Result<(Vec<Stmt>, Option<KeywordData>)> {
|
||||
fn match_properties_and_total<'a>(
|
||||
args: &'a [Expr],
|
||||
keywords: &'a [Keyword],
|
||||
) -> Result<(Vec<Stmt>, Option<&'a Keyword>)> {
|
||||
// We don't have to manage the hybrid case because it's not possible to have a
|
||||
// dict and keywords. For example, the following is illegal:
|
||||
// MyType = TypedDict('MyType', {'a': int, 'b': str}, a=int, b=str)
|
||||
// ```
|
||||
// MyType = TypedDict('MyType', {'a': int, 'b': str}, a=int, b=str)
|
||||
// ```
|
||||
if let Some(dict) = args.get(1) {
|
||||
let total = get_total_from_only_keyword(keywords).cloned();
|
||||
let total = match_total_from_only_keyword(keywords);
|
||||
match &dict.node {
|
||||
ExprKind::Dict { keys, values } => {
|
||||
Ok((get_properties_from_dict_literal(keys, values)?, total))
|
||||
Ok((properties_from_dict_literal(keys, values)?, total))
|
||||
}
|
||||
ExprKind::Call { func, keywords, .. } => {
|
||||
Ok((get_properties_from_dict_call(func, keywords)?, total))
|
||||
Ok((properties_from_dict_call(func, keywords)?, total))
|
||||
}
|
||||
_ => Ok((vec![create_pass_stmt()], total)),
|
||||
}
|
||||
} else if !keywords.is_empty() {
|
||||
Ok((get_properties_from_keywords(keywords)?, None))
|
||||
Ok((properties_from_keywords(keywords)?, None))
|
||||
} else {
|
||||
Ok((vec![create_pass_stmt()], None))
|
||||
}
|
||||
@@ -196,19 +173,15 @@ fn convert_to_class(
|
||||
stmt: &Stmt,
|
||||
class_name: &str,
|
||||
body: Vec<Stmt>,
|
||||
total_keyword: Option<KeywordData>,
|
||||
base_class: &ExprKind,
|
||||
total_keyword: Option<&Keyword>,
|
||||
base_class: &Expr,
|
||||
stylist: &SourceCodeStyleDetector,
|
||||
) -> Fix {
|
||||
let mut generator: SourceCodeGenerator = stylist.into();
|
||||
generator.unparse_stmt(&create_class_def_stmt(
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
));
|
||||
Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_stmt(
|
||||
&create_class_def_stmt(class_name, body, total_keyword, base_class),
|
||||
stylist,
|
||||
),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
)
|
||||
@@ -226,26 +199,25 @@ pub fn convert_typed_dict_functional_to_class(
|
||||
{
|
||||
return;
|
||||
};
|
||||
let (body, total_keyword) = match get_properties_and_total(args, keywords) {
|
||||
Err(err) => {
|
||||
error!("Failed to parse TypedDict: {err}");
|
||||
return;
|
||||
}
|
||||
Ok(args) => args,
|
||||
};
|
||||
|
||||
let mut check = Check::new(
|
||||
violations::ConvertTypedDictFunctionalToClass(class_name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
check.amend(convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.style,
|
||||
));
|
||||
match match_properties_and_total(args, keywords) {
|
||||
Ok((body, total_keyword)) => {
|
||||
check.amend(convert_to_class(
|
||||
stmt,
|
||||
class_name,
|
||||
body,
|
||||
total_keyword,
|
||||
base_class,
|
||||
checker.style,
|
||||
));
|
||||
}
|
||||
Err(err) => debug!("Skipping ineligible `TypedDict` \"{class_name}\": {err}"),
|
||||
};
|
||||
}
|
||||
checker.checks.push(check);
|
||||
}
|
||||
|
||||
@@ -138,6 +138,16 @@ expression: checks
|
||||
row: 24
|
||||
column: 65
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType9
|
||||
location:
|
||||
row: 27
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 55
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass: MyType10
|
||||
location:
|
||||
|
||||
@@ -53,4 +53,14 @@ expression: checks
|
||||
row: 15
|
||||
column: 56
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass: NT4
|
||||
location:
|
||||
row: 18
|
||||
column: 0
|
||||
end_location:
|
||||
row: 22
|
||||
column: 1
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -651,29 +651,50 @@ impl AlwaysAutofixableViolation for FStringMissingPlaceholders {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct MultiValueRepeatedKeyLiteral;
|
||||
pub struct MultiValueRepeatedKeyLiteral(pub String, pub bool);
|
||||
);
|
||||
impl Violation for MultiValueRepeatedKeyLiteral {
|
||||
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 {
|
||||
MultiValueRepeatedKeyLiteral
|
||||
MultiValueRepeatedKeyLiteral("...".to_string(), true)
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct MultiValueRepeatedKeyVariable(pub String);
|
||||
pub struct MultiValueRepeatedKeyVariable(pub String, pub bool);
|
||||
);
|
||||
impl Violation for MultiValueRepeatedKeyVariable {
|
||||
fn message(&self) -> String {
|
||||
let MultiValueRepeatedKeyVariable(name) = self;
|
||||
let MultiValueRepeatedKeyVariable(name, ..) = self;
|
||||
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 {
|
||||
MultiValueRepeatedKeyVariable("...".to_string())
|
||||
MultiValueRepeatedKeyVariable("...".to_string(), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user