Compare commits

..

21 Commits

Author SHA1 Message Date
Charlie Marsh
5576db3d5a Bump version to 0.0.99 2022-11-03 11:47:15 -04:00
Charlie Marsh
f26f38d023 Enable autofix for C406 (#570) 2022-11-03 11:27:46 -04:00
Charlie Marsh
578ec4d843 Implement autofix for C416 (#568) 2022-11-03 11:05:08 -04:00
Charlie Marsh
2f3bebe5a2 Enable autofix for dict(a=1)-like dictionaries (#567) 2022-11-03 10:42:54 -04:00
Charlie Marsh
22991e3e0e Bump version to 0.0.98 2022-11-03 10:09:33 -04:00
Charlie Marsh
242bdf86b1 Use ::ruff to ensure ruff imports come first 2022-11-03 10:09:28 -04:00
Charlie Marsh
a3f7de2257 Make --quiet more aggressive (#566) 2022-11-03 10:08:15 -04:00
Charlie Marsh
9f9cbb5520 Improve pyproject.toml examples in README.md 2022-11-03 09:33:17 -04:00
Charlie Marsh
937c83d57f Remove crates subdirectory (#563) 2022-11-03 09:19:54 -04:00
Charlie Marsh
e00bcd19f5 Bump version to 0.0.97 2022-11-02 22:38:43 -04:00
Charlie Marsh
4550581be2 Relax lowercase condition in N806 (#562) 2022-11-02 22:38:29 -04:00
Charlie Marsh
b42d77a4c6 Avoid autofixes for errors in f-strings (#561) 2022-11-02 22:31:57 -04:00
Charlie Marsh
e473df1fe9 Bump version to 0.0.96 2022-11-02 22:10:56 -04:00
Charlie Marsh
d448281b33 Add plugin properties to settings cache key (#559) 2022-11-02 22:10:01 -04:00
Charlie Marsh
94597fefc1 Change flake8-quotes default to double quotes (#558) 2022-11-02 22:03:54 -04:00
Charlie Marsh
add0bdeeb7 DRY up utilities in flake8_comprehensions/fixes.rs (#556) 2022-11-02 22:00:53 -04:00
Charlie Marsh
6a180b95d1 Implement autofix for dict and tuple comprehensions (#555) 2022-11-02 21:36:20 -04:00
Charlie Marsh
416c338237 Respect trailing whitespace in comprehension fixes (#554) 2022-11-02 21:08:17 -04:00
Charlie Marsh
9948be0145 Automatically fix a variety of comprehension rules (#553) 2022-11-02 20:39:35 -04:00
Charlie Marsh
f50ff61056 Expose autofix mode in public API (#551) 2022-11-02 09:44:46 -04:00
Charlie Marsh
017fec2bc5 Set override in actions-rs/toolchain@v1 (#543) 2022-11-02 09:36:22 -04:00
58 changed files with 1642 additions and 292 deletions

View File

@@ -38,6 +38,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: rustfmt
- uses: actions/cache@v3
env:
@@ -62,6 +63,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: clippy
- uses: actions/cache@v3
env:
@@ -86,6 +88,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -109,6 +112,7 @@ jobs:
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/setup-python@v4
with:
python-version: "3.10"

View File

@@ -30,7 +30,7 @@ jobs:
uses: messense/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist --sdist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel - x86_64
run: |
@@ -57,7 +57,7 @@ jobs:
- name: Build wheels - universal2
uses: messense/maturin-action@v1
with:
args: --release --universal2 --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel - universal2
run: |
@@ -89,7 +89,7 @@ jobs:
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
shell: bash
@@ -117,7 +117,7 @@ jobs:
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
if: matrix.target == 'x86_64'
@@ -144,7 +144,7 @@ jobs:
with:
target: ${{ matrix.target }}
manylinux: auto
args: --no-default-features --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --no-default-features --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- uses: uraimo/run-on-arch-action@v2.0.5
if: matrix.target != 'ppc64'
@@ -183,7 +183,7 @@ jobs:
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
if: matrix.target == 'x86_64-unknown-linux-musl'
@@ -219,7 +219,7 @@ jobs:
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- uses: uraimo/run-on-arch-action@master
name: Install built wheel
@@ -261,7 +261,7 @@ jobs:
maturin-version: "v0.13.0"
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
if: matrix.target == 'x86_64'
run: |

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.95
rev: v0.0.99
hooks:
- id: ruff

4
Cargo.lock generated
View File

@@ -920,7 +920,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.95-dev.0"
version = "0.0.99-dev.0"
dependencies = [
"anyhow",
"clap 4.0.15",
@@ -2211,7 +2211,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.95"
version = "0.0.99"
dependencies = [
"anyhow",
"assert_cmd",

View File

@@ -1,11 +1,11 @@
[workspace]
members = [
"crates/flake8_to_ruff",
"flake8_to_ruff",
]
[package]
name = "ruff"
version = "0.0.95"
version = "0.0.99"
edition = "2021"
[lib]

View File

@@ -89,7 +89,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.95
rev: v0.0.99
hooks:
- id: ruff
```
@@ -99,15 +99,59 @@ _Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as
## Configuration
Ruff is configurable both via `pyproject.toml` and the command line.
For example, you could configure Ruff to only enforce a subset of rules with:
Ruff is configurable both via `pyproject.toml` and the command line. If left unspecified, the
default configuration is equivalent to:
```toml
[tool.ruff]
line-length = 88
# Enable Flake's "E" and "F" codes by default.
select = ["E", "F"]
ignore = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".hg",
".mypy_cache",
".nox",
".pants.d",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
per-file-ignores = {}
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Assume Python 3.10.
target-version = "py310"
```
As an example, the following would configure Ruff to (1) avoid checking for line-length
violations (`E501`) and (2) ignore unused import rules in `__init__.py` files:
```toml
[tool.ruff]
select = ["E", "F"]
# Never enforce `E501`.
ignore = ["E501"]
# Ignore `F401` violations in any `__init__.py` file, and in `path/to/file.py`.
per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]}
```
@@ -115,7 +159,8 @@ Plugin configurations should be expressed as subsections, e.g.:
```toml
[tool.ruff]
line-length = 88
# Add "Q" to the list of enabled codes.
select = ["E", "F", "Q"]
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
@@ -143,6 +188,8 @@ Options:
-v, --verbose
Enable verbose logging
-q, --quiet
Only log errors
-s, --silent
Disable all logging (but still exit with status code "1" upon detecting errors)
-e, --exit-zero
Exit with status code "0", even upon detecting errors
@@ -392,21 +439,21 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-bugbear
@@ -521,7 +568,7 @@ including:
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
@@ -545,7 +592,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.95"
version = "0.0.99"
dependencies = [
"anyhow",
"clap",
@@ -1410,7 +1410,7 @@ dependencies = [
name = "net2"
version = "0.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530.0.98408837c631"
dependencies = [
"cfg-if 0.1.10",
"libc",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.95"
version = "0.0.99"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.95-dev.0"
version = "0.0.99-dev.0"
edition = "2021"
[lib]
@@ -12,7 +12,7 @@ clap = { version = "4.0.1", features = ["derive"] }
configparser = { version = "3.0.2" }
once_cell = { version = "1.13.1" }
regex = { version = "1.6.0" }
ruff = { path = "../..", default-features = false }
ruff = { path = "..", default-features = false }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }

View File

@@ -1 +1,4 @@
x = list(x for x in range(3))
x = list(
x for x in range(3)
)

View File

@@ -1 +1,4 @@
x = set(x for x in range(3))
x = set(
x for x in range(3)
)

View File

@@ -1,2 +1,5 @@
dict((x, x) for x in range(3))
dict(
(x, x) for x in range(3)
)
dict(((x, x) for x in range(3)), z=3)

View File

@@ -1 +1,4 @@
s = set([x for x in range(3)])
s = set(
[x for x in range(3)]
)

View File

@@ -1,3 +1,10 @@
t1 = tuple([1, 2])
t2 = tuple((1, 2))
t3 = tuple([])
t1 = tuple([])
t2 = tuple([1, 2])
t3 = tuple((1, 2))
t4 = tuple([
1,
2
])
t5 = tuple(
(1, 2)
)

View File

@@ -2,3 +2,4 @@ def f():
lower = 0
Camel = 0
CONSTANT = 0
_ = 0

1
rust-toolchain Normal file
View File

@@ -0,0 +1 @@
1.64.0

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "1.64.0"

View File

@@ -6,6 +6,8 @@ use rustpython_parser::ast::Location;
use crate::autofix::{Fix, Patch};
use crate::checks::Check;
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
// higher levels in the execution flow.
#[derive(Hash)]
pub enum Mode {
Generate,

View File

@@ -129,7 +129,9 @@ impl<'a> Checker<'a> {
/// Return `true` if a patch should be generated under the given autofix
/// `Mode`.
pub fn patch(&self) -> bool {
self.autofix.patch()
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
// location data.
self.autofix.patch() && self.in_f_string.is_none()
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -989,7 +991,13 @@ where
// flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_list(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -997,7 +1005,13 @@ where
if self.settings.enabled.contains(&CheckCode::C401) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1005,7 +1019,13 @@ where
if self.settings.enabled.contains(&CheckCode::C402) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_generator_dict(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1014,7 +1034,13 @@ where
if self.settings.enabled.contains(&CheckCode::C403) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1024,7 +1050,10 @@ where
if self.settings.enabled.contains(&CheckCode::C404) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
expr, func, args, keywords,
func,
args,
keywords,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1033,7 +1062,13 @@ where
if self.settings.enabled.contains(&CheckCode::C405) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_set(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1041,7 +1076,13 @@ where
if self.settings.enabled.contains(&CheckCode::C406) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_literal_dict(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1049,7 +1090,13 @@ where
if self.settings.enabled.contains(&CheckCode::C408) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
expr, func, args, keywords,
expr,
func,
args,
keywords,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
@@ -1058,7 +1105,12 @@ where
if self.settings.enabled.contains(&CheckCode::C409) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
expr, func, args,
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1068,7 +1120,12 @@ where
if self.settings.enabled.contains(&CheckCode::C410) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
expr, func, args,
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1076,9 +1133,14 @@ where
}
if self.settings.enabled.contains(&CheckCode::C411) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_call(expr, func, args)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_list_call(
expr,
func,
args,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
}
@@ -1086,7 +1148,9 @@ where
if self.settings.enabled.contains(&CheckCode::C413) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_call_around_sorted(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1096,7 +1160,9 @@ where
if self.settings.enabled.contains(&CheckCode::C414) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1106,7 +1172,9 @@ where
if self.settings.enabled.contains(&CheckCode::C415) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_subscript_reversal(
expr, func, args,
func,
args,
self.locate_check(Range::from_located(expr)),
)
{
self.checks.push(check);
@@ -1114,9 +1182,11 @@ where
}
if self.settings.enabled.contains(&CheckCode::C417) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_map(expr, func, args)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_map(
func,
args,
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};
}
@@ -1293,7 +1363,12 @@ where
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
expr, elt, generators,
expr,
elt,
generators,
self.locator,
self.patch(),
self.locate_check(Range::from_located(expr)),
) {
self.checks.push(check);
};

View File

@@ -1469,6 +1469,17 @@ impl CheckKind {
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnnecessaryCollectionCall(_)
| CheckKind::UnnecessaryComprehension(_)
| CheckKind::UnnecessaryGeneratorDict
| CheckKind::UnnecessaryGeneratorList
| CheckKind::UnnecessaryGeneratorSet
| CheckKind::UnnecessaryListCall
| CheckKind::UnnecessaryListComprehensionSet
| CheckKind::UnnecessaryLiteralDict(_)
| CheckKind::UnnecessaryLiteralSet(_)
| CheckKind::UnnecessaryLiteralWithinListCall(_)
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
| CheckKind::UnusedImport(_, false)
| CheckKind::UnusedLoopControlVariable(_)
| CheckKind::UnusedNOQA(_)

View File

@@ -7,6 +7,7 @@ use log::warn;
use regex::Regex;
use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::configuration::Configuration;
use crate::settings::types::{PatternPrefixPair, PythonVersion};
@@ -93,6 +94,19 @@ pub struct Cli {
pub stdin_filename: Option<String>,
}
/// Map the CLI settings to a `LogLevel`.
pub fn extract_log_level(cli: &Cli) -> LogLevel {
if cli.silent {
LogLevel::Silent
} else if cli.quiet {
LogLevel::Quiet
} else if cli.verbose {
LogLevel::Verbose
} else {
LogLevel::Default
}
}
pub enum Warnable {
Select,
ExtendSelect,

19
src/cst/matchers.rs Normal file
View File

@@ -0,0 +1,19 @@
use anyhow::Result;
use libcst_native::Module;
use rustpython_ast::Located;
use crate::ast::types::Range;
use crate::source_code_locator::SourceCodeLocator;
pub fn match_tree<'a, T>(
locator: &'a SourceCodeLocator,
located: &'a Located<T>,
) -> Result<Module<'a>> {
match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(located)),
None,
) {
Ok(module) => Ok(module),
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
}
}

View File

@@ -1 +1,2 @@
pub mod helpers;
pub mod matchers;

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -42,7 +42,10 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
..
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
let mut check = Check::new(
CheckKind::DoNotAssertFalse,
checker.locate_check(Range::from_located(test)),
);
if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -17,7 +17,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
{
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,
Range::from_located(stmt),
checker.locate_check(Range::from_located(stmt)),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind, Unaryop};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -11,7 +11,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
if matches!(op, Unaryop::UAdd) {
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
checker.locate_check(Range::from_located(expr)),
))
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use rustpython_ast::{Expr, ExprKind, Stmt};
use crate::ast::types::Range;
use crate::ast::types::{CheckLocator, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::autofix::Fix;
@@ -64,7 +64,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
let mut check = Check::new(
CheckKind::UnusedLoopControlVariable(name.to_string()),
Range::from_located(expr),
checker.locate_check(Range::from_located(expr)),
);
if checker.patch() {
// Prefix the variable name with an underscore.

View File

@@ -1,3 +1,4 @@
use log::error;
use num_bigint::BigInt;
use rustpython_ast::{
Comprehension, Constant, Expr, ExprKind, Keyword, KeywordData, Located, Unaryop,
@@ -5,6 +6,8 @@ use rustpython_ast::{
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_comprehensions::fixes;
use crate::source_code_locator::SourceCodeLocator;
fn function_name(func: &Expr) -> Option<&str> {
if let ExprKind::Name { id, .. } = &func.node {
@@ -49,13 +52,20 @@ pub fn unnecessary_generator_list(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("list", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorList, location);
if fix {
match fixes::fix_unnecessary_generator_list(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
None
}
@@ -66,13 +76,20 @@ pub fn unnecessary_generator_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::GeneratorExp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorSet,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorSet, location);
if fix {
match fixes::fix_unnecessary_generator_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
None
}
@@ -83,15 +100,22 @@ pub fn unnecessary_generator_dict(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
if let ExprKind::GeneratorExp { elt, .. } = argument {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorDict,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryGeneratorDict, location);
if fix {
match fixes::fix_unnecessary_generator_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
_ => {}
}
@@ -105,23 +129,30 @@ pub fn unnecessary_list_comprehension_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
if let ExprKind::ListComp { .. } = &argument {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryListComprehensionSet, location);
if fix {
match fixes::fix_unnecessary_list_comprehension_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
None
}
/// C404 (`dict([...])`)
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
if let ExprKind::ListComp { elt, .. } = &argument {
@@ -129,7 +160,7 @@ pub fn unnecessary_list_comprehension_dict(
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
location,
));
}
_ => {}
@@ -144,6 +175,9 @@ pub fn unnecessary_literal_set(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("set", func, args, keywords)?;
let kind = match argument {
@@ -151,10 +185,14 @@ pub fn unnecessary_literal_set(
ExprKind::Tuple { .. } => "tuple",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryLiteralSet(kind.to_string()),
Range::from_located(expr),
))
let mut check = Check::new(CheckKind::UnnecessaryLiteralSet(kind.to_string()), location);
if fix {
match fixes::fix_unnecessary_literal_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C406 (`dict([(1, 2)])`)
@@ -163,6 +201,9 @@ pub fn unnecessary_literal_dict(
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
let (kind, elts) = match argument {
@@ -170,18 +211,24 @@ pub fn unnecessary_literal_dict(
ExprKind::List { elts, .. } => ("list", elts),
_ => return None,
};
if let Some(elt) = elts.first() {
// dict((1, 2), ...)) or dict([(1, 2), ...])
if !matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2) {
return None;
// Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`.
if !elts
.iter()
.all(|elt| matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2))
{
return None;
}
let mut check = Check::new(
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
location,
);
if fix {
match fixes::fix_unnecessary_literal_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(Check::new(
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
Range::from_located(expr),
))
Some(check)
}
/// C408
@@ -190,22 +237,34 @@ pub fn unnecessary_collection_call(
func: &Expr,
args: &[Expr],
keywords: &[Located<KeywordData>],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
if !args.is_empty() {
return None;
}
let id = function_name(func)?;
match id {
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (),
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => {
// `dict()` or `dict(a=1)` (as opposed to `dict(**a)`)
}
"list" | "tuple" => {
// list() or tuple()
// `list()` or `tuple()`
}
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_collection_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C409
@@ -213,6 +272,9 @@ pub fn unnecessary_literal_within_tuple_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("tuple", func, args)?;
let argument_kind = match argument {
@@ -220,10 +282,17 @@ pub fn unnecessary_literal_within_tuple_call(
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall(argument_kind.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_literal_within_tuple_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C410
@@ -231,6 +300,9 @@ pub fn unnecessary_literal_within_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?;
let argument_kind = match argument {
@@ -238,26 +310,48 @@ pub fn unnecessary_literal_within_list_call(
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryLiteralWithinListCall(argument_kind.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_literal_within_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C411
pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
pub fn unnecessary_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?;
if let ExprKind::ListComp { .. } = argument {
return Some(Check::new(
CheckKind::UnnecessaryListCall,
Range::from_located(expr),
));
let mut check = Check::new(CheckKind::UnnecessaryListCall, location);
if fix {
match fixes::fix_unnecessary_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
None
}
/// C413
pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
pub fn unnecessary_call_around_sorted(
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !(outer == "list" || outer == "reversed") {
return None;
@@ -266,7 +360,7 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
if function_name(func)? == "sorted" {
return Some(Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
Range::from_located(expr),
location,
));
}
}
@@ -275,19 +369,19 @@ pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -
/// C414
pub fn unnecessary_double_cast_or_process(
expr: &Expr,
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
fn new_check(inner: &str, outer: &str, expr: &Expr) -> Check {
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
Range::from_located(expr),
location,
)
}
@@ -297,24 +391,28 @@ pub fn unnecessary_double_cast_or_process(
if (outer == "set" || outer == "sorted")
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
{
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
// Ex) list(tuple(...))
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(new_check(inner, outer, expr));
return Some(new_check(inner, outer, location));
}
}
None
}
/// C415
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
pub fn unnecessary_subscript_reversal(
func: &Expr,
args: &[Expr],
location: Range,
) -> Option<Check> {
let first_arg = args.first()?;
let id = function_name(func)?;
if !["set", "sorted", "reversed"].contains(&id) {
@@ -337,7 +435,7 @@ pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -
if *val == BigInt::from(1) {
return Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(id.to_string()),
Range::from_located(expr),
location,
));
}
}
@@ -354,6 +452,9 @@ pub fn unnecessary_comprehension(
expr: &Expr,
elt: &Expr,
generators: &[Comprehension],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
if generators.len() != 1 {
return None;
@@ -372,32 +473,36 @@ pub fn unnecessary_comprehension(
ExprKind::SetComp { .. } => "set",
_ => return None,
};
Some(Check::new(
let mut check = Check::new(
CheckKind::UnnecessaryComprehension(expr_kind.to_string()),
Range::from_located(expr),
))
location,
);
if fix {
match fixes::fix_unnecessary_comprehension(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
Some(check)
}
/// C417
pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
fn new_check(kind: &str, expr: &Expr) -> Check {
Check::new(
CheckKind::UnnecessaryMap(kind.to_string()),
Range::from_located(expr),
)
pub fn unnecessary_map(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
fn new_check(kind: &str, location: Range) -> Check {
Check::new(CheckKind::UnnecessaryMap(kind.to_string()), location)
}
let id = function_name(func)?;
match id {
"map" => {
if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) {
return Some(new_check("generator", expr));
return Some(new_check("generator", location));
}
}
"list" | "set" => {
if let ExprKind::Call { func, args, .. } = &args.first()?.node {
let argument = first_argument_with_matching_function("map", func, args)?;
if let ExprKind::Lambda { .. } = argument {
return Some(new_check(id, expr));
return Some(new_check(id, location));
}
}
}
@@ -408,7 +513,7 @@ pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check>
if let ExprKind::Lambda { body, .. } = &argument {
if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2)
{
return Some(new_check(id, expr));
return Some(new_check(id, location));
}
}
}

View File

@@ -0,0 +1,664 @@
use anyhow::Result;
use libcst_native::{
Arg, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression, LeftCurlyBrace,
LeftParen, LeftSquareBracket, List, ListComp, Module, Name, ParenthesizableWhitespace,
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
SmallStatement, Statement, Tuple,
};
use crate::autofix::Fix;
use crate::cst::matchers::match_tree;
use crate::source_code_locator::SourceCodeLocator;
fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
Ok(expr)
} else {
Err(anyhow::anyhow!("Expected node to be: SmallStatement::Expr"))
}
} else {
Err(anyhow::anyhow!("Expected node to be: Statement::Simple"))
}
}
fn match_call<'a, 'b>(expr: &'a mut Expr<'b>) -> Result<&'a mut Call<'b>> {
if let Expression::Call(call) = &mut expr.value {
Ok(call)
} else {
Err(anyhow::anyhow!("Expected node to be: Expression::Call"))
}
}
fn match_arg<'a, 'b>(call: &'a Call<'b>) -> Result<&'a Arg<'b>> {
if let Some(arg) = call.args.first() {
Ok(arg)
} else {
Err(anyhow::anyhow!("Expected node to be: Arg"))
}
}
/// (C400) Convert `list(x for x in y)` to `[x for x in y]`.
pub fn fix_unnecessary_generator_list(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
};
body.value = Expression::ListComp(Box::new(ListComp {
elt: generator_exp.elt.clone(),
for_in: generator_exp.for_in.clone(),
lbracket: LeftSquareBracket {
whitespace_after: call.whitespace_before_args.clone(),
},
rbracket: RightSquareBracket {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: generator_exp.lpar.clone(),
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C401) Convert `set(x for x in y)` to `{x for x in y}`.
pub fn fix_unnecessary_generator_set(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
};
body.value = Expression::SetComp(Box::new(SetComp {
elt: generator_exp.elt.clone(),
for_in: generator_exp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: generator_exp.lpar.clone(),
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C402) Convert `dict((x, x) for x in range(3))` to `{x: x for x in
/// range(3)}`.
pub fn fix_unnecessary_generator_dict(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
// Extract the (k, v) from `(k, v) for ...`.
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
};
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
tuple
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
};
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
value
} else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the first element"
));
};
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
value
} else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the second element"
));
};
body.value = Expression::DictComp(Box::new(DictComp {
key: Box::new(key.clone()),
value: Box::new(value.clone()),
for_in: generator_exp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C403) Convert `set([x for x in y])` to `{x for x in y}`.
pub fn fix_unnecessary_list_comprehension_set(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(ListComp)))) ->
// Expr(SetComp)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
body.value = Expression::SetComp(Box::new(SetComp {
elt: list_comp.elt.clone(),
for_in: list_comp.for_in.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: list_comp.lpar.clone(),
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
pub fn fix_unnecessary_literal_set(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(Set)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let mut call = match_call(body)?;
let arg = match_arg(call)?;
let elements = match &arg.value {
Expression::Tuple(inner) => &inner.elements,
Expression::List(inner) => &inner.elements,
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List"
))
}
};
if elements.is_empty() {
call.args = vec![];
} else {
body.value = Expression::Set(Box::new(Set {
elements: elements.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
}));
}
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C406) Convert `dict([(1, 2)])` to `{1: 2}`.
pub fn fix_unnecessary_literal_dict(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(Dict)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let elements = match &arg.value {
Expression::Tuple(inner) => &inner.elements,
Expression::List(inner) => &inner.elements,
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List"
))
}
};
let elements: Vec<DictElement> = elements
.iter()
.map(|element| {
if let Element::Simple {
value: Expression::Tuple(tuple),
comma,
} = element
{
if let Some(Element::Simple { value: key, .. }) = tuple.elements.get(0) {
if let Some(Element::Simple { value, .. }) = tuple.elements.get(1) {
return Ok(DictElement::Simple {
key: key.clone(),
value: value.clone(),
comma: comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
});
}
}
}
Err(anyhow::anyhow!(
"Expected each argument to be a tuple of length two"
))
})
.collect::<Result<Vec<DictElement>>>()?;
body.value = Expression::Dict(Box::new(Dict {
elements,
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C408)
pub fn fix_unnecessary_collection_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call("list" | "tuple" | "dict")))) -> Expr(List|Tuple|Dict)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let name = if let Expression::Name(name) = &call.func.as_ref() {
name
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Name"));
};
// Arena allocator used to create formatted strings of sufficient lifetime,
// below.
let mut arena: Vec<String> = vec![];
match name.value {
"tuple" => {
body.value = Expression::Tuple(Box::new(Tuple {
elements: Default::default(),
lpar: vec![Default::default()],
rpar: vec![Default::default()],
}));
}
"list" => {
body.value = Expression::List(Box::new(List {
elements: Default::default(),
lbracket: Default::default(),
rbracket: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
}));
}
"dict" => {
if call.args.is_empty() {
body.value = Expression::Dict(Box::new(Dict {
elements: Default::default(),
lbrace: Default::default(),
rbrace: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
}));
} else {
// Quote each argument.
for arg in &call.args {
let quoted = format!(
"\"{}\"",
arg.keyword
.as_ref()
.expect("Expected dictionary argument to be kwarg")
.value
);
arena.push(quoted);
}
let elements = call
.args
.iter()
.enumerate()
.map(|(i, arg)| DictElement::Simple {
key: Expression::SimpleString(Box::new(SimpleString {
value: &arena[i],
lpar: Default::default(),
rpar: Default::default(),
})),
value: arg.value.clone(),
comma: arg.comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
})
.collect();
body.value = Expression::Dict(Box::new(Dict {
elements,
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: call
.args
.last()
.expect("Arguments should be non-empty")
.whitespace_after_arg
.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
}));
}
}
_ => {
return Err(anyhow::anyhow!("Expected function name to be one of: \
'tuple', 'list', 'dict'"
.to_string()));
}
};
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C409) Convert `tuple([1, 2])` to `tuple(1, 2)`
pub fn fix_unnecessary_literal_within_tuple_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let (elements, whitespace_after, whitespace_before) = match &arg.value {
Expression::Tuple(inner) => (
&inner.elements,
&inner
.lpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
.whitespace_after,
&inner
.rpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
.whitespace_before,
),
Expression::List(inner) => (
&inner.elements,
&inner.lbracket.whitespace_after,
&inner.rbracket.whitespace_before,
),
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List"
))
}
};
body.value = Expression::Tuple(Box::new(Tuple {
elements: elements.clone(),
lpar: vec![LeftParen {
whitespace_after: whitespace_after.clone(),
}],
rpar: vec![RightParen {
whitespace_before: whitespace_before.clone(),
}],
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C410) Convert `list([1, 2])` to `[1, 2]`
pub fn fix_unnecessary_literal_within_list_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let (elements, whitespace_after, whitespace_before) = match &arg.value {
Expression::Tuple(inner) => (
&inner.elements,
&inner
.lpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
.whitespace_after,
&inner
.rpar
.first()
.ok_or_else(|| anyhow::anyhow!("Expected at least one set of parentheses"))?
.whitespace_before,
),
Expression::List(inner) => (
&inner.elements,
&inner.lbracket.whitespace_after,
&inner.rbracket.whitespace_before,
),
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::Tuple | Expression::List"
))
}
};
body.value = Expression::List(Box::new(List {
elements: elements.clone(),
lbracket: LeftSquareBracket {
whitespace_after: whitespace_after.clone(),
},
rbracket: RightSquareBracket {
whitespace_before: whitespace_before.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C411) Convert `list([i * i for i in x])` to `[i * i for i in x]`.
pub fn fix_unnecessary_list_call(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
body.value = arg.value.clone();
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C416) Convert `[i for i in x]` to `list(x)`.
pub fn fix_unnecessary_comprehension(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let mut tree = match_tree(locator, expr)?;
let mut body = match_expr(&mut tree)?;
match &body.value {
Expression::ListComp(inner) => {
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "list",
lpar: Default::default(),
rpar: Default::default(),
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
}
Expression::SetComp(inner) => {
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "set",
lpar: Default::default(),
rpar: Default::default(),
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
}
_ => {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::ListComp | Expression:SetComp"
))
}
}
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}

View File

@@ -1 +1,2 @@
pub mod checks;
mod fixes;

View File

@@ -9,25 +9,20 @@ pub fn print_call(
func: &Expr,
check_print: bool,
check_pprint: bool,
location: Range,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
return Some(Check::new(CheckKind::PPrintFound, location));
}
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
return Some(Check::new(CheckKind::PPrintFound, location));
}
}
}

View File

@@ -1,6 +1,7 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::checks::CheckCode;
@@ -12,6 +13,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
checker.locate_check(Range::from_located(expr)),
) {
if checker.patch() {
let context = checker.binding_context();

View File

@@ -2,7 +2,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Quote {
Single,
@@ -18,7 +18,7 @@ pub struct Options {
pub avoid_escape: Option<bool>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub inline_quotes: Quote,
pub multiline_quotes: Quote,
@@ -29,7 +29,7 @@ pub struct Settings {
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
inline_quotes: options.inline_quotes.unwrap_or(Quote::Single),
inline_quotes: options.inline_quotes.unwrap_or(Quote::Double),
multiline_quotes: options.multiline_quotes.unwrap_or(Quote::Double),
docstring_quotes: options.docstring_quotes.unwrap_or(Quote::Double),
avoid_escape: options.avoid_escape.unwrap_or(true),
@@ -40,7 +40,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Self {
inline_quotes: Quote::Single,
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,

View File

@@ -45,8 +45,8 @@ pub mod settings;
pub mod source_code_locator;
pub mod visibility;
/// Run ruff over Python source code directly.
pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
/// Run Ruff over Python source code directly.
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
match &project_root {
@@ -75,7 +75,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Check>> {
tokens,
&noqa_line_for,
&settings,
&Mode::None,
&if autofix { Mode::Generate } else { Mode::None },
)?;
Ok(checks)

View File

@@ -15,7 +15,36 @@ macro_rules! tell_user {
}
}
pub fn set_up_logging(verbose: bool) -> Result<()> {
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum LogLevel {
// No output (+ `log::LevelFilter::Off`).
Silent,
// Only show lint violations, with no decorative output (+ `log::LevelFilter::Off`).
Quiet,
// All user-facing output (+ `log::LevelFilter::Info`).
Default,
// All user-facing output (+ `log::LevelFilter::Debug`).
Verbose,
}
impl LogLevel {
fn level_filter(&self) -> log::LevelFilter {
match self {
LogLevel::Default => log::LevelFilter::Info,
LogLevel::Verbose => log::LevelFilter::Debug,
LogLevel::Quiet => log::LevelFilter::Off,
LogLevel::Silent => log::LevelFilter::Off,
}
}
}
impl Default for LogLevel {
fn default() -> Self {
LogLevel::Default
}
}
pub fn set_up_logging(level: &LogLevel) -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
@@ -26,12 +55,22 @@ pub fn set_up_logging(verbose: bool) -> Result<()> {
message
))
})
.level(if verbose {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.level(level.level_filter())
.chain(std::io::stdout())
.apply()
.map_err(|e| e.into())
}
#[cfg(test)]
mod tests {
use crate::logging::LogLevel;
#[test]
fn ordering() {
assert!(LogLevel::Default > LogLevel::Silent);
assert!(LogLevel::Default >= LogLevel::Default);
assert!(LogLevel::Quiet > LogLevel::Silent);
assert!(LogLevel::Verbose > LogLevel::Default);
assert!(LogLevel::Verbose > LogLevel::Silent);
}
}

View File

@@ -5,6 +5,20 @@ use std::process::ExitCode;
use std::sync::mpsc::channel;
use std::time::Instant;
#[cfg(not(target_family = "wasm"))]
use ::ruff::cache;
use ::ruff::checks::{CheckCode, CheckKind};
use ::ruff::checks_gen::CheckCodePrefix;
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, warn_on, Cli, Warnable};
use ::ruff::fs::iter_python_files;
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::FilePattern;
use ::ruff::settings::user::UserConfiguration;
use ::ruff::settings::{pyproject, Settings};
use anyhow::Result;
use clap::Parser;
use colored::Colorize;
@@ -12,21 +26,6 @@ use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
#[cfg(not(target_family = "wasm"))]
use ruff::cache;
use ruff::checks::{CheckCode, CheckKind};
use ruff::checks_gen::CheckCodePrefix;
use ruff::cli::{collect_per_file_ignores, warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin};
use ruff::logging::set_up_logging;
use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
use ruff::settings::configuration::Configuration;
use ruff::settings::types::FilePattern;
use ruff::settings::user::UserConfiguration;
use ruff::settings::{pyproject, Settings};
use ruff::tell_user;
use walkdir::DirEntry;
#[cfg(feature = "update-informer")]
@@ -223,10 +222,10 @@ fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
}
fn inner_main() -> Result<ExitCode> {
let mut cli = Cli::parse();
cli.quiet |= cli.silent;
let cli = Cli::parse();
set_up_logging(cli.verbose)?;
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&cli.files);
@@ -320,7 +319,7 @@ fn inner_main() -> Result<ExitCode> {
#[cfg(not(target_family = "wasm"))]
cache::init()?;
let mut printer = Printer::new(cli.format, cli.verbose);
let printer = Printer::new(&cli.format, &log_level);
if cli.watch {
if cli.fix {
eprintln!("Warning: --fix is not enabled in watch mode.");
@@ -340,12 +339,10 @@ fn inner_main() -> Result<ExitCode> {
// Perform an initial run instantly.
printer.clear_screen()?;
tell_user!("Starting linter in watch mode...\n");
printer.write_to_user("Starting linter in watch mode...\n");
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
if !cli.silent {
printer.write_continuously(&messages)?;
}
printer.write_continuously(&messages)?;
// Configure the file watcher.
let (tx, rx) = channel();
@@ -360,12 +357,10 @@ fn inner_main() -> Result<ExitCode> {
if let Some(path) = e.path {
if path.to_string_lossy().ends_with(".py") {
printer.clear_screen()?;
tell_user!("File change detected...\n");
printer.write_to_user("File change detected...\n");
let messages = run_once(&cli.files, &settings, !cli.no_cache, false)?;
if !cli.silent {
printer.write_continuously(&messages)?;
}
printer.write_continuously(&messages)?;
}
}
}
@@ -374,37 +369,36 @@ fn inner_main() -> Result<ExitCode> {
}
} else if cli.add_noqa {
let modifications = add_noqa(&cli.files, &settings)?;
if modifications > 0 {
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications = autoformat(&cli.files, &settings)?;
if modifications > 0 {
if modifications > 0 && log_level >= LogLevel::Default {
println!("Formatted {modifications} files.");
}
} else {
let (messages, should_print_messages, should_check_updates) =
if cli.files == vec![PathBuf::from("-")] {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
(
run_once_stdin(&settings, path, cli.fix)?,
!cli.silent && !cli.fix,
false,
)
} else {
(
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?,
!cli.silent,
!cli.quiet,
)
};
if should_print_messages {
let is_stdin = cli.files == vec![PathBuf::from("-")];
// Generate lint violations.
let messages = if is_stdin {
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
let path = Path::new(&filename);
run_once_stdin(&settings, path, cli.fix)?
} else {
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?
};
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && cli.fix) {
printer.write_once(&messages)?;
}
// Check for updates if we're in a non-silent log level.
#[cfg(feature = "update-informer")]
if should_check_updates {
if !is_stdin && log_level >= LogLevel::Default {
check_for_updates();
}

View File

@@ -118,7 +118,7 @@ pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
return None;
}
if !is_lower(name) {
if name.to_lowercase() != name {
return Some(Check::new(
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
Range::from_located(expr),

View File

@@ -29,7 +29,7 @@ pub struct Options {
pub staticmethod_decorators: Option<Vec<String>>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub ignore_names: Vec<String>,
pub classmethod_decorators: Vec<String>,

View File

@@ -5,6 +5,7 @@ use rustpython_parser::ast::Location;
use serde::Serialize;
use crate::checks::{CheckCode, CheckKind};
use crate::logging::LogLevel;
use crate::message::Message;
use crate::tell_user;
@@ -25,17 +26,27 @@ struct ExpandedMessage<'a> {
filename: &'a String,
}
pub struct Printer {
format: SerializationFormat,
verbose: bool,
pub struct Printer<'a> {
format: &'a SerializationFormat,
log_level: &'a LogLevel,
}
impl Printer {
pub fn new(format: SerializationFormat, verbose: bool) -> Self {
Self { format, verbose }
impl<'a> Printer<'a> {
pub fn new(format: &'a SerializationFormat, log_level: &'a LogLevel) -> Self {
Self { format, log_level }
}
pub fn write_once(&mut self, messages: &[Message]) -> Result<()> {
pub fn write_to_user(&self, message: &str) {
if self.log_level >= &LogLevel::Default {
tell_user!("{}", message);
}
}
pub fn write_once(&self, messages: &[Message]) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
}
let (fixed, outstanding): (Vec<&Message>, Vec<&Message>) =
messages.iter().partition(|message| message.fixed);
let num_fixable = outstanding
@@ -64,22 +75,26 @@ impl Printer {
)
}
SerializationFormat::Text => {
if !fixed.is_empty() {
println!(
"Found {} error(s) ({} fixed).",
outstanding.len(),
fixed.len()
)
} else if !outstanding.is_empty() || self.verbose {
println!("Found {} error(s).", outstanding.len())
if self.log_level >= &LogLevel::Default {
if !fixed.is_empty() {
println!(
"Found {} error(s) ({} fixed).",
outstanding.len(),
fixed.len()
)
} else if !outstanding.is_empty() {
println!("Found {} error(s).", outstanding.len())
}
}
for message in outstanding {
println!("{}", message)
}
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.")
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.")
}
}
}
}
@@ -87,14 +102,22 @@ impl Printer {
Ok(())
}
pub fn write_continuously(&mut self, messages: &[Message]) -> Result<()> {
tell_user!(
"Found {} error(s). Watching for file changes.",
messages.len(),
);
pub fn write_continuously(&self, messages: &[Message]) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
}
if self.log_level >= &LogLevel::Default {
tell_user!(
"Found {} error(s). Watching for file changes.",
messages.len(),
);
}
if !messages.is_empty() {
println!();
if self.log_level >= &LogLevel::Default {
println!();
}
for message in messages {
println!("{}", message)
}
@@ -103,7 +126,7 @@ impl Printer {
Ok(())
}
pub fn clear_screen(&mut self) -> Result<()> {
pub fn clear_screen(&self) -> Result<()> {
#[cfg(not(target_family = "wasm"))]
clearscreen::clear()?;
Ok(())

View File

@@ -2,9 +2,9 @@ use anyhow::Result;
use libcst_native::{Codegen, ImportNames, NameOrAttribute, SmallStatement, Statement};
use rustpython_ast::Stmt;
use crate::ast::types::Range;
use crate::autofix::{helpers, Fix};
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_tree;
use crate::source_code_locator::SourceCodeLocator;
/// Generate a Fix to remove any unused imports from an `import` statement.
@@ -15,24 +15,18 @@ pub fn remove_unused_imports(
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let mut tree = match_tree(locator, stmt)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
};
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
"Expected node to be: SmallStatement::ImportFrom"
));
};
let aliases = &mut body.names;
@@ -78,31 +72,25 @@ pub fn remove_unused_import_froms(
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let mut tree = match_tree(locator, stmt)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
"Expected node to be: SmallStatement::ImportFrom"
));
};
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
};
// Preserve the trailing comma (or not) from the last entry.

View File

@@ -84,14 +84,19 @@ impl Settings {
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.line_length.hash(state);
// Add base properties in alphabetical order.
self.dummy_variable_rgx.as_str().hash(state);
for value in self.enabled.iter() {
value.hash(state);
}
self.line_length.hash(state);
for value in self.per_file_ignores.iter() {
value.hash(state);
}
self.target_version.hash(state);
// Add plugin properties in alphabetical order.
self.flake8_quotes.hash(state);
self.pep8_naming.hash(state);
}
}

View File

@@ -11,7 +11,8 @@ use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::fs;
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "lowercase")]
pub enum PythonVersion {
Py33,
Py34,

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
column: 29
fix: ~
fix:
patch:
content: "[x for x in range(3)]"
location:
row: 1
column: 4
end_location:
row: 1
column: 29
applied: false
- kind: UnnecessaryGeneratorList
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "[\n x for x in range(3)\n]"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
column: 28
fix: ~
fix:
patch:
content: "{x for x in range(3)}"
location:
row: 1
column: 4
end_location:
row: 1
column: 28
applied: false
- kind: UnnecessaryGeneratorSet
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "{\n x for x in range(3)\n}"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
column: 30
fix: ~
fix:
patch:
content: "{x: x for x in range(3)}"
location:
row: 1
column: 0
end_location:
row: 1
column: 30
applied: false
- kind: UnnecessaryGeneratorDict
location:
row: 2
column: 0
end_location:
row: 4
column: 1
fix:
patch:
content: "{\n x: x for x in range(3)\n}"
location:
row: 2
column: 0
end_location:
row: 4
column: 1
applied: false

View File

@@ -9,5 +9,31 @@ expression: checks
end_location:
row: 1
column: 30
fix: ~
fix:
patch:
content: "{x for x in range(3)}"
location:
row: 1
column: 4
end_location:
row: 1
column: 30
applied: false
- kind: UnnecessaryListComprehensionSet
location:
row: 2
column: 4
end_location:
row: 4
column: 1
fix:
patch:
content: "{\n x for x in range(3)\n}"
location:
row: 2
column: 4
end_location:
row: 4
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 16
fix: ~
fix:
patch:
content: "{1, 2}"
location:
row: 1
column: 5
end_location:
row: 1
column: 16
applied: false
- kind:
UnnecessaryLiteralSet: tuple
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 16
fix: ~
fix:
patch:
content: "{1, 2}"
location:
row: 2
column: 5
end_location:
row: 2
column: 16
applied: false
- kind:
UnnecessaryLiteralSet: list
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 12
fix: ~
fix:
patch:
content: set()
location:
row: 3
column: 5
end_location:
row: 3
column: 12
applied: false
- kind:
UnnecessaryLiteralSet: tuple
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
column: 12
fix: ~
fix:
patch:
content: set()
location:
row: 4
column: 5
end_location:
row: 4
column: 12
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 19
fix: ~
fix:
patch:
content: "{1: 2}"
location:
row: 1
column: 5
end_location:
row: 1
column: 19
applied: false
- kind:
UnnecessaryLiteralDict: tuple
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 20
fix: ~
fix:
patch:
content: "{1: 2,}"
location:
row: 2
column: 5
end_location:
row: 2
column: 20
applied: false
- kind:
UnnecessaryLiteralDict: list
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 13
fix: ~
fix:
patch:
content: "{}"
location:
row: 3
column: 5
end_location:
row: 3
column: 13
applied: false
- kind:
UnnecessaryLiteralDict: tuple
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
column: 13
fix: ~
fix:
patch:
content: "{}"
location:
row: 4
column: 5
end_location:
row: 4
column: 13
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 11
fix: ~
fix:
patch:
content: ()
location:
row: 1
column: 4
end_location:
row: 1
column: 11
applied: false
- kind:
UnnecessaryCollectionCall: list
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 10
fix: ~
fix:
patch:
content: "[]"
location:
row: 2
column: 4
end_location:
row: 2
column: 10
applied: false
- kind:
UnnecessaryCollectionCall: dict
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 11
fix: ~
fix:
patch:
content: "{}"
location:
row: 3
column: 5
end_location:
row: 3
column: 11
applied: false
- kind:
UnnecessaryCollectionCall: dict
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
column: 14
fix: ~
fix:
patch:
content: "{\"a\": 1}"
location:
row: 4
column: 5
end_location:
row: 4
column: 14
applied: false

View File

@@ -9,24 +9,87 @@ expression: checks
column: 5
end_location:
row: 1
column: 18
fix: ~
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 2
column: 5
end_location:
row: 2
column: 18
fix: ~
column: 14
fix:
patch:
content: ()
location:
row: 1
column: 5
end_location:
row: 1
column: 14
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 2
column: 5
end_location:
row: 2
column: 18
fix:
patch:
content: "(1, 2)"
location:
row: 2
column: 5
end_location:
row: 2
column: 18
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 3
column: 5
end_location:
row: 3
column: 14
fix: ~
column: 18
fix:
patch:
content: "(1, 2)"
location:
row: 3
column: 5
end_location:
row: 3
column: 18
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 4
column: 5
end_location:
row: 7
column: 2
fix:
patch:
content: "(\n 1,\n 2\n)"
location:
row: 4
column: 5
end_location:
row: 7
column: 2
applied: false
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 8
column: 5
end_location:
row: 10
column: 1
fix:
patch:
content: "(1, 2)"
location:
row: 8
column: 5
end_location:
row: 10
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 1
column: 17
fix: ~
fix:
patch:
content: "[1, 2]"
location:
row: 1
column: 5
end_location:
row: 1
column: 17
applied: false
- kind:
UnnecessaryLiteralWithinListCall: tuple
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 2
column: 17
fix: ~
fix:
patch:
content: "[1, 2]"
location:
row: 2
column: 5
end_location:
row: 2
column: 17
applied: false
- kind:
UnnecessaryLiteralWithinListCall: list
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 3
column: 13
fix: ~
fix:
patch:
content: "[]"
location:
row: 3
column: 5
end_location:
row: 3
column: 13
applied: false
- kind:
UnnecessaryLiteralWithinListCall: tuple
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 4
column: 13
fix: ~
fix:
patch:
content: "[]"
location:
row: 4
column: 5
end_location:
row: 4
column: 13
applied: false

View File

@@ -9,5 +9,14 @@ expression: checks
end_location:
row: 2
column: 20
fix: ~
fix:
patch:
content: "[i for i in x]"
location:
row: 2
column: 0
end_location:
row: 2
column: 20
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 2
column: 14
fix: ~
fix:
patch:
content: list(x)
location:
row: 2
column: 0
end_location:
row: 2
column: 14
applied: false
- kind:
UnnecessaryComprehension: set
location:
@@ -19,5 +28,14 @@ expression: checks
end_location:
row: 3
column: 14
fix: ~
fix:
patch:
content: set(x)
location:
row: 3
column: 0
end_location:
row: 3
column: 14
applied: false