Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f74050e5b1 | ||
|
|
90b2d85c85 | ||
|
|
ccf848705d | ||
|
|
3b535fcc74 | ||
|
|
06321fd240 | ||
|
|
cdae2f0e67 | ||
|
|
f52691a90a | ||
|
|
07e47bef4b | ||
|
|
86b61806a5 | ||
|
|
31ce37dd8e | ||
|
|
2cf6d05586 | ||
|
|
65c34c56d6 |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.206
|
||||
rev: v0.0.207
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ free to submit a PR. For larger changes (e.g., new lint rules, new functionality
|
||||
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
|
||||
your proposed change.
|
||||
|
||||
If you're looking for a place to start, we recommend implementing a new lint rule (see:
|
||||
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
|
||||
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
|
||||
existing Python plugins, which can be used as a reference implementation.
|
||||
|
||||
As a concrete example: consider taking on one of the rules in [`flake8-simplify`](https://github.com/charliermarsh/ruff/issues/998),
|
||||
and looking to the originating [Python source](https://github.com/MartinThoma/flake8-simplify) for
|
||||
guidance.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ruff is written in Rust. You'll need to install the
|
||||
@@ -65,21 +74,15 @@ understand how other, similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run Ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`. Once you're satisfied with the
|
||||
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
|
||||
section of `src/linter.rs`, like so:
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule.
|
||||
|
||||
```rust
|
||||
use test_case::test_case;
|
||||
Run `cargo +nightly dev generate-all` to generate the code for your new fixture. Then run Ruff
|
||||
locally with (e.g.) `cargo run resources/test/fixtures/E402.py --no-cache --select E402`.
|
||||
|
||||
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
|
||||
...
|
||||
```
|
||||
|
||||
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
|
||||
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
|
||||
rest of your changes.
|
||||
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
|
||||
`test_case` macro in the relevant `src/[test-suite-name]/mod.rs` file. Then, run `cargo test`. Your
|
||||
test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the generated
|
||||
snapshot, then commit the snapshot file alongside the rest of your changes.
|
||||
|
||||
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.206-dev.0"
|
||||
version = "0.0.207-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1878,7 +1878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1946,7 +1946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1967,7 +1967,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.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.206", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.207", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "71becd4059fdce4bce7010f1208ed3b1c883abba" }
|
||||
|
||||
10
README.md
10
README.md
@@ -157,9 +157,9 @@ pacman -S ruff
|
||||
To run Ruff, try any of the following:
|
||||
|
||||
```shell
|
||||
ruff path/to/code/to/check.py
|
||||
ruff path/to/code/
|
||||
ruff path/to/code/*.py
|
||||
ruff path/to/code/to/check.py # Run Ruff over `check.py`
|
||||
ruff path/to/code/ # Run Ruff over all files in `/path/to/code` (and any subdirectories)
|
||||
ruff path/to/code/*.py # Run Ruff over all `.py` files in `/path/to/code`
|
||||
```
|
||||
|
||||
You can run Ruff in `--watch` mode to automatically re-run on-change:
|
||||
@@ -173,7 +173,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.206'
|
||||
rev: 'v0.0.207'
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Respect `exclude` and `extend-exclude` settings.
|
||||
@@ -689,6 +689,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| UP024 | OSErrorAlias | Replace aliased errors with `OSError` | 🛠 |
|
||||
| UP025 | RewriteUnicodeLiteral | Remove unicode literals from strings | 🛠 |
|
||||
| UP026 | RewriteMockImport | `mock` is deprecated, use `unittest.mock` | 🛠 |
|
||||
| UP027 | RewriteListComprehension | Replace unpacked list comprehension with a generator expression | 🛠 |
|
||||
|
||||
### pep8-naming (N)
|
||||
|
||||
@@ -922,6 +923,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 |
|
||||
| SIM300 | YodaConditions | Use `left == right` instead of `right == left (Yoda-conditions)` | |
|
||||
|
||||
### flake8-tidy-imports (TID)
|
||||
|
||||
|
||||
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.206"
|
||||
version = "0.0.207"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.206-dev.0"
|
||||
version = "0.0.207-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
|
||||
11
resources/test/fixtures/flake8_simplify/SIM300.py
vendored
Normal file
11
resources/test/fixtures/flake8_simplify/SIM300.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Errors
|
||||
"yoda" == compare # SIM300
|
||||
42 == age # SIM300
|
||||
|
||||
# OK
|
||||
compare == "yoda"
|
||||
age == 42
|
||||
x == y
|
||||
"yoda" == compare == 1
|
||||
"yoda" == compare == someothervar
|
||||
"yoda" == "yoda"
|
||||
0
resources/test/fixtures/pydocstyle/D104/__init__.py
vendored
Normal file
0
resources/test/fixtures/pydocstyle/D104/__init__.py
vendored
Normal file
6
resources/test/fixtures/pyflakes/F841_2.py
vendored
Normal file
6
resources/test/fixtures/pyflakes/F841_2.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# test case for https://github.com/charliermarsh/ruff/issues/1552
|
||||
def _():
|
||||
x = 0
|
||||
list()[x:]
|
||||
18
resources/test/fixtures/pyupgrade/UP027.py
vendored
Normal file
18
resources/test/fixtures/pyupgrade/UP027.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Should change
|
||||
foo, bar, baz = [fn(x) for x in items]
|
||||
|
||||
foo, bar, baz =[fn(x) for x in items]
|
||||
|
||||
foo, bar, baz = [fn(x) for x in items]
|
||||
|
||||
foo, bar, baz = [[i for i in fn(x)] for x in items]
|
||||
|
||||
foo, bar, baz = [
|
||||
fn(x)
|
||||
for x in items
|
||||
]
|
||||
|
||||
# Should not change
|
||||
foo = [fn(x) for x in items]
|
||||
|
||||
x, = [await foo for foo in bar]
|
||||
@@ -848,6 +848,9 @@
|
||||
"SIM1",
|
||||
"SIM11",
|
||||
"SIM118",
|
||||
"SIM3",
|
||||
"SIM30",
|
||||
"SIM300",
|
||||
"T",
|
||||
"T1",
|
||||
"T10",
|
||||
@@ -912,6 +915,7 @@
|
||||
"UP024",
|
||||
"UP025",
|
||||
"UP026",
|
||||
"UP027",
|
||||
"W",
|
||||
"W2",
|
||||
"W29",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.206"
|
||||
version = "0.0.207"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -212,6 +212,23 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
is_constant(expr) && !is_singleton(expr)
|
||||
}
|
||||
|
||||
/// Return `true` if an `Expr` is not a reference to a variable (or something
|
||||
/// that could resolve to a variable, like a function call).
|
||||
pub fn is_non_variable(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
expr.node,
|
||||
ExprKind::Constant { .. }
|
||||
| ExprKind::Tuple { .. }
|
||||
| ExprKind::List { .. }
|
||||
| ExprKind::Set { .. }
|
||||
| ExprKind::Dict { .. }
|
||||
| ExprKind::SetComp { .. }
|
||||
| ExprKind::ListComp { .. }
|
||||
| ExprKind::DictComp { .. }
|
||||
| ExprKind::GeneratorExp { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the `Keyword` with the given name, if it's present in the list of
|
||||
/// `Keyword` arguments.
|
||||
pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> {
|
||||
|
||||
@@ -1210,12 +1210,11 @@ where
|
||||
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP001) {
|
||||
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B003) {
|
||||
flake8_bugbear::plugins::assignment_to_os_environ(self, targets);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::S105) {
|
||||
if let Some(check) =
|
||||
flake8_bandit::plugins::assign_hardcoded_password_string(value, targets)
|
||||
@@ -1223,6 +1222,10 @@ where
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP001) {
|
||||
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP013) {
|
||||
pyupgrade::plugins::convert_typed_dict_functional_to_class(
|
||||
self, stmt, targets, value,
|
||||
@@ -1233,6 +1236,10 @@ where
|
||||
self, stmt, targets, value,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP027) {
|
||||
pyupgrade::plugins::unpack_list_comprehension(self, targets, value);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::PD901) {
|
||||
if let Some(check) = pandas_vet::checks::assignment_to_df(targets) {
|
||||
self.add_check(check);
|
||||
@@ -1581,7 +1588,7 @@ where
|
||||
pylint::plugins::used_prior_global_declaration(self, id, expr);
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if !self.in_deferred_string_type_definition
|
||||
&& self.settings.enabled.contains(&CheckCode::UP006)
|
||||
@@ -1622,6 +1629,16 @@ where
|
||||
] {
|
||||
if self.settings.enabled.contains(&code) {
|
||||
if attr == name {
|
||||
// Avoid flagging on function calls (e.g., `df.values()`).
|
||||
if let Some(parent) = self.current_expr_parent() {
|
||||
if matches!(parent.0.node, ExprKind::Call { .. }) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Avoid flagging on non-DataFrames (e.g., `{"a": 1}.values`).
|
||||
if helpers::is_non_variable(value) {
|
||||
continue;
|
||||
}
|
||||
self.add_check(Check::new(code.kind(), Range::from_located(expr)));
|
||||
};
|
||||
}
|
||||
@@ -2385,6 +2402,10 @@ where
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::SIM300) {
|
||||
flake8_simplify::plugins::yoda_conditions(self, expr, left, ops, comparators);
|
||||
}
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
|
||||
@@ -213,6 +213,7 @@ pub enum CheckCode {
|
||||
YTT303,
|
||||
// flake8-simplify
|
||||
SIM118,
|
||||
SIM300,
|
||||
// pyupgrade
|
||||
UP001,
|
||||
UP003,
|
||||
@@ -239,6 +240,7 @@ pub enum CheckCode {
|
||||
UP024,
|
||||
UP025,
|
||||
UP026,
|
||||
UP027,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -892,6 +894,7 @@ pub enum CheckKind {
|
||||
SysVersionSlice1Referenced,
|
||||
// flake8-simplify
|
||||
KeyInDict(String, String),
|
||||
YodaConditions(String, String),
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UselessMetaclassType,
|
||||
@@ -918,6 +921,7 @@ pub enum CheckKind {
|
||||
OSErrorAlias(Option<String>),
|
||||
RewriteUnicodeLiteral,
|
||||
RewriteMockImport(MockReference),
|
||||
RewriteListComprehension,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -1285,6 +1289,7 @@ impl CheckCode {
|
||||
CheckCode::BLE001 => CheckKind::BlindExcept("Exception".to_string()),
|
||||
// flake8-simplify
|
||||
CheckCode::SIM118 => CheckKind::KeyInDict("key".to_string(), "dict".to_string()),
|
||||
CheckCode::SIM300 => CheckKind::YodaConditions("left".to_string(), "right".to_string()),
|
||||
// pyupgrade
|
||||
CheckCode::UP001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::UP003 => CheckKind::TypeOfPrimitive(Primitive::Str),
|
||||
@@ -1314,6 +1319,7 @@ impl CheckCode {
|
||||
CheckCode::UP024 => CheckKind::OSErrorAlias(None),
|
||||
CheckCode::UP025 => CheckKind::RewriteUnicodeLiteral,
|
||||
CheckCode::UP026 => CheckKind::RewriteMockImport(MockReference::Import),
|
||||
CheckCode::UP027 => CheckKind::RewriteListComprehension,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -1718,6 +1724,7 @@ impl CheckCode {
|
||||
CheckCode::S106 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::SIM118 => CheckCategory::Flake8Simplify,
|
||||
CheckCode::SIM300 => CheckCategory::Flake8Simplify,
|
||||
CheckCode::T100 => CheckCategory::Flake8Debugger,
|
||||
CheckCode::T201 => CheckCategory::Flake8Print,
|
||||
CheckCode::T203 => CheckCategory::Flake8Print,
|
||||
@@ -1748,6 +1755,7 @@ impl CheckCode {
|
||||
CheckCode::UP024 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP025 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP026 => CheckCategory::Pyupgrade,
|
||||
CheckCode::UP027 => CheckCategory::Pyupgrade,
|
||||
CheckCode::W292 => CheckCategory::Pycodestyle,
|
||||
CheckCode::W605 => CheckCategory::Pycodestyle,
|
||||
CheckCode::YTT101 => CheckCategory::Flake82020,
|
||||
@@ -1946,6 +1954,7 @@ impl CheckKind {
|
||||
CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303,
|
||||
// flake8-simplify
|
||||
CheckKind::KeyInDict(..) => &CheckCode::SIM118,
|
||||
CheckKind::YodaConditions(..) => &CheckCode::SIM300,
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(..) => &CheckCode::UP003,
|
||||
CheckKind::UselessMetaclassType => &CheckCode::UP001,
|
||||
@@ -1972,6 +1981,7 @@ impl CheckKind {
|
||||
CheckKind::OSErrorAlias(..) => &CheckCode::UP024,
|
||||
CheckKind::RewriteUnicodeLiteral => &CheckCode::UP025,
|
||||
CheckKind::RewriteMockImport(..) => &CheckCode::UP026,
|
||||
CheckKind::RewriteListComprehension => &CheckCode::UP027,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
|
||||
@@ -2668,6 +2678,9 @@ impl CheckKind {
|
||||
CheckKind::KeyInDict(key, dict) => {
|
||||
format!("Use `{key} in {dict}` instead of `{key} in {dict}.keys()`")
|
||||
}
|
||||
CheckKind::YodaConditions(left, right) => {
|
||||
format!("Use `{left} == {right}` instead of `{right} == {left} (Yoda-conditions)`")
|
||||
}
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -2736,6 +2749,9 @@ impl CheckKind {
|
||||
CheckKind::RewriteMockImport(..) => {
|
||||
"`mock` is deprecated, use `unittest.mock`".to_string()
|
||||
}
|
||||
CheckKind::RewriteListComprehension => {
|
||||
"Replace unpacked list comprehension with a generator expression".to_string()
|
||||
}
|
||||
// pydocstyle
|
||||
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
|
||||
CheckKind::BlankLineAfterSummary => {
|
||||
@@ -3206,6 +3222,7 @@ impl CheckKind {
|
||||
| CheckKind::RewriteCElementTree
|
||||
| CheckKind::RewriteMockImport(..)
|
||||
| CheckKind::RewriteUnicodeLiteral
|
||||
| CheckKind::RewriteListComprehension
|
||||
| CheckKind::SectionNameEndsInColon(..)
|
||||
| CheckKind::SectionNotOverIndented(..)
|
||||
| CheckKind::SectionUnderlineAfterName(..)
|
||||
@@ -3321,6 +3338,9 @@ impl CheckKind {
|
||||
MockReference::Import => "Import from `unittest.mock` instead".to_string(),
|
||||
MockReference::Attribute => "Replace `mock.mock` with `mock`".to_string(),
|
||||
}),
|
||||
CheckKind::RewriteListComprehension => {
|
||||
Some("Replace with generator expression".to_string())
|
||||
}
|
||||
CheckKind::NewLineAfterSectionName(name) => {
|
||||
Some(format!("Add newline after \"{name}\""))
|
||||
}
|
||||
|
||||
@@ -479,6 +479,9 @@ pub enum CheckCodePrefix {
|
||||
SIM1,
|
||||
SIM11,
|
||||
SIM118,
|
||||
SIM3,
|
||||
SIM30,
|
||||
SIM300,
|
||||
T,
|
||||
T1,
|
||||
T10,
|
||||
@@ -543,6 +546,7 @@ pub enum CheckCodePrefix {
|
||||
UP024,
|
||||
UP025,
|
||||
UP026,
|
||||
UP027,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -753,6 +757,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::YTT302,
|
||||
CheckCode::YTT303,
|
||||
CheckCode::SIM118,
|
||||
CheckCode::SIM300,
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
@@ -778,6 +783,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
CheckCode::D100,
|
||||
CheckCode::D101,
|
||||
CheckCode::D102,
|
||||
@@ -2409,10 +2415,13 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::S105 => vec![CheckCode::S105],
|
||||
CheckCodePrefix::S106 => vec![CheckCode::S106],
|
||||
CheckCodePrefix::S107 => vec![CheckCode::S107],
|
||||
CheckCodePrefix::SIM => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM => vec![CheckCode::SIM118, CheckCode::SIM300],
|
||||
CheckCodePrefix::SIM1 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM11 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM118 => vec![CheckCode::SIM118],
|
||||
CheckCodePrefix::SIM3 => vec![CheckCode::SIM300],
|
||||
CheckCodePrefix::SIM30 => vec![CheckCode::SIM300],
|
||||
CheckCodePrefix::SIM300 => vec![CheckCode::SIM300],
|
||||
CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T1 => vec![CheckCode::T100],
|
||||
CheckCodePrefix::T10 => vec![CheckCode::T100],
|
||||
@@ -2459,6 +2468,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
@@ -2494,6 +2504,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
@@ -2713,6 +2724,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
],
|
||||
CheckCodePrefix::UP0 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2740,6 +2752,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
],
|
||||
CheckCodePrefix::UP00 => vec![
|
||||
CheckCode::UP001,
|
||||
@@ -2789,6 +2802,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::UP024,
|
||||
CheckCode::UP025,
|
||||
CheckCode::UP026,
|
||||
CheckCode::UP027,
|
||||
],
|
||||
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
|
||||
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
|
||||
@@ -2797,6 +2811,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP024 => vec![CheckCode::UP024],
|
||||
CheckCodePrefix::UP025 => vec![CheckCode::UP025],
|
||||
CheckCodePrefix::UP026 => vec![CheckCode::UP026],
|
||||
CheckCodePrefix::UP027 => vec![CheckCode::UP027],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -3307,6 +3322,9 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::SIM1 => SuffixLength::One,
|
||||
CheckCodePrefix::SIM11 => SuffixLength::Two,
|
||||
CheckCodePrefix::SIM118 => SuffixLength::Three,
|
||||
CheckCodePrefix::SIM3 => SuffixLength::One,
|
||||
CheckCodePrefix::SIM30 => SuffixLength::Two,
|
||||
CheckCodePrefix::SIM300 => SuffixLength::Three,
|
||||
CheckCodePrefix::T => SuffixLength::Zero,
|
||||
CheckCodePrefix::T1 => SuffixLength::One,
|
||||
CheckCodePrefix::T10 => SuffixLength::Two,
|
||||
@@ -3371,6 +3389,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::UP024 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP025 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP026 => SuffixLength::Three,
|
||||
CheckCodePrefix::UP027 => SuffixLength::Three,
|
||||
CheckCodePrefix::W => SuffixLength::Zero,
|
||||
CheckCodePrefix::W2 => SuffixLength::One,
|
||||
CheckCodePrefix::W29 => SuffixLength::Two,
|
||||
|
||||
@@ -46,8 +46,8 @@ pub fn run(
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"{}: {}",
|
||||
"warning".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(Diagnostics::default());
|
||||
@@ -196,8 +196,8 @@ pub fn add_noqa(
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"{}: {}",
|
||||
"warning".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(0);
|
||||
@@ -270,8 +270,8 @@ pub fn show_files(
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{} {}",
|
||||
"warning:".yellow().bold(),
|
||||
"{}: {}",
|
||||
"warning".yellow().bold(),
|
||||
"No Python files found under the given path(s)"
|
||||
);
|
||||
return Ok(());
|
||||
|
||||
@@ -24,7 +24,7 @@ static CODING_COMMENT_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)").unwrap());
|
||||
static HASH_NUMBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d").unwrap());
|
||||
static MULTILINE_ASSIGNMENT_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\s*\w+\s*=.*[(\[{]$").unwrap());
|
||||
Lazy::new(|| Regex::new(r"^\s*([(\[]\s*)?(\w+\s*,\s*)*\w+\s*([)\]]\s*)?=.*[(\[{]$").unwrap());
|
||||
static PARTIAL_DICTIONARY_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"^\s*['"]\w+['"]\s*:.+[,{]\s*$"#).unwrap());
|
||||
static PRINT_RETURN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap());
|
||||
@@ -153,6 +153,21 @@ mod tests {
|
||||
assert!(!comment_contains_code("#or else:"));
|
||||
assert!(!comment_contains_code("#else True:"));
|
||||
|
||||
// Unpacking assignments
|
||||
assert!(comment_contains_code(
|
||||
"# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create("
|
||||
));
|
||||
assert!(comment_contains_code(
|
||||
"# (user_content_type, _) = TimelineEvent.objects.using(db_alias).get_or_create("
|
||||
));
|
||||
assert!(comment_contains_code(
|
||||
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create("
|
||||
));
|
||||
assert!(comment_contains_code(
|
||||
"# app_label=\"core\", model=\"user\""
|
||||
));
|
||||
assert!(comment_contains_code("# )"));
|
||||
|
||||
// TODO(charlie): This should be `true` under aggressive mode.
|
||||
assert!(!comment_contains_code("#def foo():"));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ mod tests {
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::SIM118, Path::new("SIM118.py"); "SIM118")]
|
||||
#[test_case(CheckCode::SIM300, Path::new("SIM300.py"); "SIM300")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let checks = test_path(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub use key_in_dict::{key_in_dict_compare, key_in_dict_for};
|
||||
pub use yoda_conditions::yoda_conditions;
|
||||
|
||||
mod key_in_dict;
|
||||
mod yoda_conditions;
|
||||
|
||||
40
src/flake8_simplify/plugins/yoda_conditions.rs
Normal file
40
src/flake8_simplify/plugins/yoda_conditions.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use rustpython_ast::{Cmpop, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// SIM300
|
||||
pub fn yoda_conditions(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
) {
|
||||
if !matches!(ops[..], [Cmpop::Eq]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if comparators.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches!(left.node, ExprKind::Constant { .. }) {
|
||||
return;
|
||||
}
|
||||
|
||||
let right = comparators.first().unwrap();
|
||||
if matches!(left.node, ExprKind::Constant { .. })
|
||||
& matches!(right.node, ExprKind::Constant { .. })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::YodaConditions(left.to_string(), right.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
|
||||
checker.add_check(check);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/flake8_simplify/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
YodaConditions:
|
||||
- "'yoda'"
|
||||
- compare
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
YodaConditions:
|
||||
- "42"
|
||||
- age
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -29,6 +29,7 @@ use ::ruff::settings::{pyproject, Settings};
|
||||
use ::ruff::updates;
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use path_absolutize::path_dedot;
|
||||
|
||||
@@ -172,13 +173,29 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
let cache = !cli.no_cache;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if cache {
|
||||
// `--no-cache` doesn't respect code changes, and so is often confusing during
|
||||
// development.
|
||||
eprintln!(
|
||||
"{}: debug build without --no-cache.",
|
||||
"warning".yellow().bold()
|
||||
);
|
||||
}
|
||||
|
||||
let printer = Printer::new(&format, &log_level, &autofix, &violations);
|
||||
if cli.watch {
|
||||
if !matches!(autofix, fixer::Mode::None) {
|
||||
eprintln!("Warning: --fix is not enabled in watch mode.");
|
||||
eprintln!(
|
||||
"{}: --fix is not enabled in watch mode.",
|
||||
"warning".yellow().bold()
|
||||
);
|
||||
}
|
||||
if format != SerializationFormat::Text {
|
||||
eprintln!("Warning: --format 'text' is used in watch mode.");
|
||||
eprintln!(
|
||||
"{}: --format 'text' is used in watch mode.",
|
||||
"warning".yellow().bold()
|
||||
);
|
||||
}
|
||||
|
||||
// Perform an initial run instantly.
|
||||
|
||||
@@ -81,8 +81,8 @@ mod tests {
|
||||
#[test_case("result = df.to_array()", &[]; "PD011_pass_to_array")]
|
||||
#[test_case("result = df.array", &[]; "PD011_pass_array")]
|
||||
#[test_case("result = df.values", &[CheckCode::PD011]; "PD011_fail_values")]
|
||||
// TODO(edgarrmondragon): Check that the attribute access is NOT a method call.
|
||||
// #[test_case("result = {}.values()", &[]; "PD011_pass_values_call")]
|
||||
#[test_case("result = df.values()", &[]; "PD011_pass_values_call")]
|
||||
#[test_case("result = {}.values", &[]; "PD011_pass_values_dict")]
|
||||
#[test_case("result = values", &[]; "PD011_pass_node_name")]
|
||||
#[test_case("employees = pd.read_csv(input_file)", &[]; "PD012_pass_read_csv")]
|
||||
#[test_case("employees = pd.read_table(input_file)", &[CheckCode::PD012]; "PD012_fail_read_table")]
|
||||
|
||||
@@ -62,6 +62,7 @@ mod tests {
|
||||
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
|
||||
#[test_case(CheckCode::D418, Path::new("D.py"); "D418")]
|
||||
#[test_case(CheckCode::D419, Path::new("D.py"); "D419")]
|
||||
#[test_case(CheckCode::D104, Path::new("D104/__init__.py"); "D104_1")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let checks = test_path(
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/pydocstyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicPackage
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -102,6 +102,7 @@ mod tests {
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F841, Path::new("F841_0.py"); "F841_0")]
|
||||
#[test_case(CheckCode::F841, Path::new("F841_1.py"); "F841_1")]
|
||||
#[test_case(CheckCode::F841, Path::new("F841_2.py"); "F841_2")]
|
||||
#[test_case(CheckCode::F842, Path::new("F842.py"); "F842")]
|
||||
#[test_case(CheckCode::F901, Path::new("F901.py"); "F901")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Expr;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
|
||||
@@ -219,6 +219,12 @@ pub fn match_annotated_subscript<F>(
|
||||
where
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
if !matches!(
|
||||
expr.node,
|
||||
ExprKind::Name { .. } | ExprKind::Attribute { .. }
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
if !call_path.is_empty() {
|
||||
for (module, member) in SUBSCRIPTS {
|
||||
|
||||
@@ -47,6 +47,7 @@ mod tests {
|
||||
#[test_case(CheckCode::UP024, Path::new("UP024_2.py"); "UP024_2")]
|
||||
#[test_case(CheckCode::UP025, Path::new("UP025.py"); "UP025")]
|
||||
#[test_case(CheckCode::UP026, Path::new("UP026.py"); "UP026")]
|
||||
#[test_case(CheckCode::UP027, Path::new("UP027.py"); "UP027")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let checks = test_path(
|
||||
|
||||
@@ -18,6 +18,7 @@ pub use typing_text_str_alias::typing_text_str_alias;
|
||||
pub use unnecessary_encode_utf8::unnecessary_encode_utf8;
|
||||
pub use unnecessary_future_import::unnecessary_future_import;
|
||||
pub use unnecessary_lru_cache_params::unnecessary_lru_cache_params;
|
||||
pub use unpack_list_comprehension::unpack_list_comprehension;
|
||||
pub use use_pep585_annotation::use_pep585_annotation;
|
||||
pub use use_pep604_annotation::use_pep604_annotation;
|
||||
pub use useless_metaclass_type::useless_metaclass_type;
|
||||
@@ -43,6 +44,7 @@ mod typing_text_str_alias;
|
||||
mod unnecessary_encode_utf8;
|
||||
mod unnecessary_future_import;
|
||||
mod unnecessary_lru_cache_params;
|
||||
mod unpack_list_comprehension;
|
||||
mod use_pep585_annotation;
|
||||
mod use_pep604_annotation;
|
||||
mod useless_metaclass_type;
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// UP025
|
||||
pub fn rewrite_unicode_literal(checker: &mut Checker, expr: &Expr, kind: &Option<String>) {
|
||||
if let Some(const_kind) = kind {
|
||||
if const_kind.to_lowercase() == "u" {
|
||||
|
||||
99
src/pyupgrade/plugins/unpack_list_comprehension.rs
Normal file
99
src/pyupgrade/plugins/unpack_list_comprehension.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
/// Returns `true` if `expr` contains an `ExprKind::Await`.
|
||||
fn contains_await(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Await { .. } => true,
|
||||
ExprKind::BoolOp { values, .. } => values.iter().any(contains_await),
|
||||
ExprKind::NamedExpr { target, value } => contains_await(target) || contains_await(value),
|
||||
ExprKind::BinOp { left, right, .. } => contains_await(left) || contains_await(right),
|
||||
ExprKind::UnaryOp { operand, .. } => contains_await(operand),
|
||||
ExprKind::Lambda { body, .. } => contains_await(body),
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
contains_await(test) || contains_await(body) || contains_await(orelse)
|
||||
}
|
||||
ExprKind::Dict { keys, values } => keys.iter().chain(values.iter()).any(contains_await),
|
||||
ExprKind::Set { elts } => elts.iter().any(contains_await),
|
||||
ExprKind::ListComp { elt, .. } => contains_await(elt),
|
||||
ExprKind::SetComp { elt, .. } => contains_await(elt),
|
||||
ExprKind::DictComp { key, value, .. } => contains_await(key) || contains_await(value),
|
||||
ExprKind::GeneratorExp { elt, .. } => contains_await(elt),
|
||||
ExprKind::Yield { value } => value.as_ref().map_or(false, |value| contains_await(value)),
|
||||
ExprKind::YieldFrom { value } => contains_await(value),
|
||||
ExprKind::Compare {
|
||||
left, comparators, ..
|
||||
} => contains_await(left) || comparators.iter().any(contains_await),
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
contains_await(func)
|
||||
|| args.iter().any(contains_await)
|
||||
|| keywords
|
||||
.iter()
|
||||
.any(|keyword| contains_await(&keyword.node.value))
|
||||
}
|
||||
ExprKind::FormattedValue {
|
||||
value, format_spec, ..
|
||||
} => {
|
||||
contains_await(value)
|
||||
|| format_spec
|
||||
.as_ref()
|
||||
.map_or(false, |value| contains_await(value))
|
||||
}
|
||||
ExprKind::JoinedStr { values } => values.iter().any(contains_await),
|
||||
ExprKind::Constant { .. } => false,
|
||||
ExprKind::Attribute { value, .. } => contains_await(value),
|
||||
ExprKind::Subscript { value, slice, .. } => contains_await(value) || contains_await(slice),
|
||||
ExprKind::Starred { value, .. } => contains_await(value),
|
||||
ExprKind::Name { .. } => false,
|
||||
ExprKind::List { elts, .. } => elts.iter().any(contains_await),
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().any(contains_await),
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
lower.as_ref().map_or(false, |value| contains_await(value))
|
||||
|| upper.as_ref().map_or(false, |value| contains_await(value))
|
||||
|| step.as_ref().map_or(false, |value| contains_await(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// UP027
|
||||
pub fn unpack_list_comprehension(checker: &mut Checker, targets: &[Expr], value: &Expr) {
|
||||
let Some(target) = targets.get(0) else {
|
||||
return;
|
||||
};
|
||||
if let ExprKind::Tuple { .. } = target.node {
|
||||
if let ExprKind::ListComp { elt, generators } = &value.node {
|
||||
if generators.iter().any(|generator| generator.is_async > 0) || contains_await(elt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::RewriteListComprehension,
|
||||
Range::from_located(value),
|
||||
);
|
||||
if checker.patch(&CheckCode::UP027) {
|
||||
let existing = checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(value));
|
||||
|
||||
let mut content = String::with_capacity(existing.len());
|
||||
content.push('(');
|
||||
content.push_str(&existing[1..existing.len() - 1]);
|
||||
content.push(')');
|
||||
check.amend(Fix::replacement(
|
||||
content,
|
||||
value.location,
|
||||
value.end_location.unwrap(),
|
||||
));
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
source: src/pyupgrade/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: RewriteListComprehension
|
||||
location:
|
||||
row: 2
|
||||
column: 16
|
||||
end_location:
|
||||
row: 2
|
||||
column: 38
|
||||
fix:
|
||||
content: (fn(x) for x in items)
|
||||
location:
|
||||
row: 2
|
||||
column: 16
|
||||
end_location:
|
||||
row: 2
|
||||
column: 38
|
||||
parent: ~
|
||||
- kind: RewriteListComprehension
|
||||
location:
|
||||
row: 4
|
||||
column: 15
|
||||
end_location:
|
||||
row: 4
|
||||
column: 37
|
||||
fix:
|
||||
content: (fn(x) for x in items)
|
||||
location:
|
||||
row: 4
|
||||
column: 15
|
||||
end_location:
|
||||
row: 4
|
||||
column: 37
|
||||
parent: ~
|
||||
- kind: RewriteListComprehension
|
||||
location:
|
||||
row: 6
|
||||
column: 25
|
||||
end_location:
|
||||
row: 6
|
||||
column: 47
|
||||
fix:
|
||||
content: (fn(x) for x in items)
|
||||
location:
|
||||
row: 6
|
||||
column: 25
|
||||
end_location:
|
||||
row: 6
|
||||
column: 47
|
||||
parent: ~
|
||||
- kind: RewriteListComprehension
|
||||
location:
|
||||
row: 8
|
||||
column: 16
|
||||
end_location:
|
||||
row: 8
|
||||
column: 51
|
||||
fix:
|
||||
content: "([i for i in fn(x)] for x in items)"
|
||||
location:
|
||||
row: 8
|
||||
column: 16
|
||||
end_location:
|
||||
row: 8
|
||||
column: 51
|
||||
parent: ~
|
||||
- kind: RewriteListComprehension
|
||||
location:
|
||||
row: 10
|
||||
column: 16
|
||||
end_location:
|
||||
row: 13
|
||||
column: 1
|
||||
fix:
|
||||
content: "(\n fn(x)\n for x in items\n)"
|
||||
location:
|
||||
row: 10
|
||||
column: 16
|
||||
end_location:
|
||||
row: 13
|
||||
column: 1
|
||||
parent: ~
|
||||
|
||||
@@ -105,17 +105,48 @@ pub fn is_init(stmt: &Stmt) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a module name indicates private visibility.
|
||||
fn is_private_module(module_name: &str) -> bool {
|
||||
module_name.starts_with('_') || (module_name.starts_with("__") && module_name.ends_with("__"))
|
||||
/// Returns `true` if a module name indicates public visibility.
|
||||
fn is_public_module(module_name: &str) -> bool {
|
||||
!module_name.starts_with('_') || (module_name.starts_with("__") && module_name.ends_with("__"))
|
||||
}
|
||||
|
||||
/// Returns `true` if a module name indicates private visibility.
|
||||
fn is_private_module(module_name: &str) -> bool {
|
||||
!is_public_module(module_name)
|
||||
}
|
||||
|
||||
/// Return the stem of a module name (everything preceding the last dot).
|
||||
fn stem(path: &str) -> &str {
|
||||
if let Some(index) = path.rfind('.') {
|
||||
&path[..index]
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the `Visibility` of the Python file at `Path` based on its name.
|
||||
pub fn module_visibility(path: &Path) -> Visibility {
|
||||
for component in path.iter().rev() {
|
||||
if is_private_module(&component.to_string_lossy()) {
|
||||
let mut components = path.iter().rev();
|
||||
|
||||
// Is the module itself private?
|
||||
// Ex) `_foo.py` (but not `__init__.py`)
|
||||
if let Some(filename) = components.next() {
|
||||
let module_name = filename.to_string_lossy();
|
||||
let module_name = stem(&module_name);
|
||||
if is_private_module(module_name) {
|
||||
return Visibility::Private;
|
||||
}
|
||||
}
|
||||
|
||||
// Is the module in a private parent?
|
||||
// Ex) `_foo/bar.py`
|
||||
for component in components {
|
||||
let module_name = component.to_string_lossy();
|
||||
if is_private_module(&module_name) {
|
||||
return Visibility::Private;
|
||||
}
|
||||
}
|
||||
|
||||
Visibility::Public
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user