Compare commits

...

49 Commits

Author SHA1 Message Date
Charlie Marsh
6fa0f21227 Bump version to 0.0.149 2022-11-30 00:51:36 -05:00
messense
f5466fe720 Add JUnit xml output format (#968) 2022-11-30 00:47:41 -05:00
Charlie Marsh
a0202e8eb2 Add Pylint parity to FAQ (#969) 2022-11-29 23:51:27 -05:00
Charlie Marsh
30ff4de9a3 Bump version to 0.0.148 2022-11-29 22:50:30 -05:00
Charlie Marsh
897b0f06ef Fix clippy 2022-11-29 22:50:20 -05:00
Charlie Marsh
2fe22a223b Bump version to 0.0.147 2022-11-29 20:17:58 -05:00
Charlie Marsh
e762dec677 Add one more note to README 2022-11-29 20:17:46 -05:00
Charlie Marsh
19baa50003 Remove extraneous key in pyproject.toml 2022-11-29 20:13:28 -05:00
Charlie Marsh
ab0df03a05 Fix pyproject tests to include pyupgrade 2022-11-29 20:11:16 -05:00
Charlie Marsh
808b348c5f Add W to pycodestyle list in README 2022-11-29 20:09:07 -05:00
Charlie Marsh
e55daa89e6 Uses dashes for README options (#966) 2022-11-29 20:08:03 -05:00
Charlie Marsh
b8e7d86696 Add pyupgrade's --keep-runtime-typing option (#965) 2022-11-29 20:05:32 -05:00
Charlie Marsh
ced7868559 Add format setting to pyproject.toml (#964) 2022-11-29 19:22:23 -05:00
Ramazan Elsunakev
7c344e8e4c feat: use more precise ranges for imports (#958) 2022-11-29 19:01:39 -05:00
Hayden
ca38c7ac48 Grouped format implementation (#954) 2022-11-29 18:45:16 -05:00
Guillaume Andreu Sabater
602291c0c2 README: fixed conf section typo (#959) 2022-11-29 09:27:02 -05:00
Charlie Marsh
d4cf376e9b Fix failing pyproject test 2022-11-29 00:00:43 -05:00
Charlie Marsh
0e6a38e6d8 Bump version to 0.0.146 2022-11-28 22:27:41 -05:00
Charlie Marsh
058fd8748d Re-generate check code prefix and rules table 2022-11-28 22:26:32 -05:00
Charlie Marsh
e8247e3ed9 Run cargo fmt 2022-11-28 22:25:09 -05:00
Charlie Marsh
ea73c717be Remove pre-commit note in README.md (#956) 2022-11-28 22:18:59 -05:00
Charlie Marsh
427e0c3158 Allow preservation of external check codes (#955) 2022-11-28 22:16:17 -05:00
Charlie Marsh
dca994d05f Bump version to 0.0.145 2022-11-28 20:57:58 -05:00
Charlie Marsh
9944246f98 Rewrite type annotations on Python 3.7 when __future__ enabled (#953) 2022-11-28 20:57:38 -05:00
Charlie Marsh
82b0b7941a Implement eradicate (#947) 2022-11-28 20:54:33 -05:00
Charlie Marsh
72453695d6 Bump version to 0.0.144 2022-11-28 20:11:08 -05:00
Charlie Marsh
1617d715f2 Allow long lines that consist of only a URL (#952) 2022-11-28 20:10:21 -05:00
pwoolvett
c4a7344791 fix(flake8_boolean_trap): add allowlist for dict methods (#943) 2022-11-28 16:17:01 -05:00
Charlie Marsh
ea9acda732 Bump version to 0.0.143 2022-11-28 15:42:25 -05:00
Anders Kaseorg
6c8021e970 Fix clippy::manual_let_else (pedantic) (#939) 2022-11-28 09:52:59 -05:00
Charlie Marsh
61b6ad46ea Allow @override methods to be undocumented (#941) 2022-11-28 09:52:12 -05:00
Anders Kaseorg
041d8108e6 Don’t require files with --explain or --generate-shell-completion (#937) 2022-11-28 00:40:20 -05:00
Charlie Marsh
e2c4a098de Bump version to 0.0.142 2022-11-28 00:19:27 -05:00
Charlie Marsh
e865f58426 Add all plugin options to README reference (#936) 2022-11-28 00:19:14 -05:00
messense
23b4e16b1d Add shell completions support (#935) 2022-11-27 23:59:36 -05:00
Charlie Marsh
ae2ac905dc Document all top-level configuration options (#934) 2022-11-27 23:50:24 -05:00
Charlie Marsh
55619b321a Run cargo fmt 2022-11-27 22:58:42 -05:00
Harutaka Kawamura
6f31b002f8 Do not enforce line length limit for comments ending with a URL (#920) 2022-11-27 22:36:17 -05:00
Charlie Marsh
1a79965aa0 Allow varargs and kwargs to be prefixed with stars (#933) 2022-11-27 22:08:27 -05:00
Charlie Marsh
16da183f8e Add some user testimonials (#932) 2022-11-27 21:55:01 -05:00
Charlie Marsh
3f689917cb Use alternative TOML format for per-file-ignores in README (#931) 2022-11-27 21:38:43 -05:00
Charlie Marsh
a4a215e8a3 Add Homebrew installation to README (#930) 2022-11-27 21:37:34 -05:00
Charlie Marsh
aa1c884910 Tweak Flake8 parity in README 2022-11-27 21:34:47 -05:00
Oliver Margetts
7fb55c6d99 F50x implementation (#919) 2022-11-27 21:30:55 -05:00
Charlie Marsh
04ea523ad8 Track aliased import-from members (#929) 2022-11-27 17:27:27 -05:00
Charlie Marsh
9897f81cf3 Bump version to 0.0.141 2022-11-26 16:33:08 -05:00
Charlie Marsh
1a2559b001 Avoid flagging redundant open modes when open is rebound (#918) 2022-11-26 16:24:41 -05:00
Denis Gavrilyuk
721a1e9443 Add flake8-debugger (#909) 2022-11-26 16:21:03 -05:00
Charlie Marsh
f38bba18ee Fix clippy warnings 2022-11-26 15:56:33 -05:00
117 changed files with 4374 additions and 1024 deletions

View File

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

101
Cargo.lock generated
View File

@@ -220,13 +220,16 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi 0.3.9",
]
@@ -290,6 +293,36 @@ dependencies = [
"termcolor",
]
[[package]]
name = "clap_complete"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
dependencies = [
"clap 4.0.22",
]
[[package]]
name = "clap_complete_command"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [
"clap 4.0.22",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "clap_complete_fig"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
dependencies = [
"clap 4.0.22",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.0.21"
@@ -670,7 +703,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.140-dev.0"
version = "0.0.149-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -853,9 +886,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
@@ -1190,6 +1223,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nextest-workspace-hack"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
[[package]]
name = "nix"
version = "0.24.2"
@@ -1576,6 +1615,29 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-junit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b909fe9bf2abb1e3d6a97c9189a37c8105c61d03dca9ce6aace023e7d682bd"
dependencies = [
"chrono",
"indexmap",
"nextest-workspace-hack",
"quick-xml",
"thiserror",
"uuid",
]
[[package]]
name = "quick-xml"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.21"
@@ -1775,7 +1837,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.140"
version = "0.0.149"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1786,6 +1848,7 @@ dependencies = [
"cachedir",
"chrono",
"clap 4.0.22",
"clap_complete_command",
"clearscreen",
"colored",
"common-path",
@@ -1804,6 +1867,7 @@ dependencies = [
"num-bigint",
"once_cell",
"path-absolutize",
"quick-junit",
"rayon",
"regex",
"ropey",
@@ -1825,7 +1889,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.140"
version = "0.0.149"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -2232,6 +2296,17 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -2458,6 +2533,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "uuid"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
[[package]]
name = "version_check"
version = "0.9.4"
@@ -2496,6 +2577,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.140"
version = "0.0.149"
edition = "2021"
rust-version = "1.65.0"
@@ -22,12 +22,13 @@ bitflags = { version = "1.3.2" }
cachedir = { version = "0.3.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive"] }
clap_complete_command = "0.4.0"
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
globset = {version = "0.4.9" }
globset = { version = "0.4.9" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
log = { version = "0.4.17" }
@@ -36,6 +37,7 @@ notify = { version = "4.0.17" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }

50
LICENSE
View File

@@ -242,6 +242,56 @@ are:
SOFTWARE.
"""
- flake8-debugger, licensed as follows:
"""
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-eradicate, licensed as follows:
"""
MIT License
Copyright (c) 2018 Nikita Sobolev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-tidy-imports, licensed as follows:
"""
MIT License

855
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.140"
version = "0.0.149"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.140"
version = "0.0.149"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.140-dev.0"
version = "0.0.149-dev.0"
edition = "2021"
[lib]

View File

@@ -248,8 +248,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -269,6 +271,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -287,8 +290,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -308,6 +313,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -326,8 +332,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -347,6 +355,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -365,8 +374,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -386,6 +397,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -404,8 +416,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -430,6 +444,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -451,8 +466,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -507,6 +524,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -525,8 +543,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -552,6 +572,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);

View File

@@ -6,17 +6,19 @@ use ruff::checks_gen::CheckCodePrefix;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
Flake8Annotations,
Flake8Bandit,
Flake8BlindExcept,
Flake8Bugbear,
Flake8Builtins,
Flake8Comprehensions,
Flake8Debugger,
Flake8Docstrings,
Flake8TidyImports,
Flake8Eradicate,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Flake8TidyImports,
McCabe,
Flake8BlindExcept,
PEP8Naming,
Pyupgrade,
}
@@ -26,16 +28,18 @@ impl FromStr for Plugin {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-bandit" => Ok(Plugin::Flake8Bandit),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"mccabe" => Ok(Plugin::McCabe),
"pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade),
@@ -47,16 +51,18 @@ impl FromStr for Plugin {
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8Bandit => CheckCodePrefix::S,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
Plugin::Flake8Debugger => CheckCodePrefix::T1,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
@@ -65,10 +71,13 @@ impl Plugin {
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
Plugin::Flake8Debugger => vec![CheckCodePrefix::T1],
Plugin::Flake8Docstrings => {
// Use the user-provided docstring.
for key in ["docstring-convention", "docstring_convention"] {
@@ -85,11 +94,10 @@ impl Plugin {
// Default to PEP8.
DocstringConvention::PEP8.select()
}
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
@@ -277,31 +285,6 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
@@ -327,6 +310,41 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-eradicate
"eradicate-aggressive" | "eradicate_aggressive" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist" | "eradicate_whitelist" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist-extend" | "eradicate_whitelist_extend" => {
plugins.insert(Plugin::Flake8Eradicate);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-tidy-imports
"ban-relative-imports" | "ban_relative_imports" => {
plugins.insert(Plugin::Flake8TidyImports);
@@ -360,16 +378,18 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Annotations,
Plugin::Flake8Bandit,
Plugin::Flake8BlindExcept,
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Debugger,
Plugin::Flake8Docstrings,
Plugin::Flake8TidyImports,
Plugin::Flake8Eradicate,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::Flake8BlindExcept,
Plugin::Flake8TidyImports,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]

View File

@@ -31,7 +31,3 @@ build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true
[tool.isort]
profile = "black"
known_third_party = ["fastapi", "pydantic", "starlette"]

View File

@@ -3,6 +3,7 @@ from functools import wraps
import os
from .expected import Expectation
from typing import overload
from typing_extensions import override
expectation = Expectation()
@@ -42,9 +43,13 @@ class class_:
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")
@override
def overridden_method(a):
return str(a)
@property
def foo(self):
"""The foo of the thing, which isn't in imperitive mood."""
"""The foo of the thing, which isn't in imperative mood."""
return "hello"
@expect('D102: Missing docstring in public method')

View File

@@ -49,3 +49,9 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""", # noqa: E501
}
# OK
# A very long URL: https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
# OK
# https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com

13
resources/test/fixtures/ERA001.py vendored Normal file
View File

@@ -0,0 +1,13 @@
#import os
# from foo import junk
#a = 3
a = 4
#foo(1, 2, 3)
def foo(x, y, z):
contentet = 1 # print('hello')
print(x, y, z)
# This is a real comment.
#return True
return False

13
resources/test/fixtures/F502.py vendored Normal file
View File

@@ -0,0 +1,13 @@
dog = {"bob": "bob"}
"%(bob)s" % dog
"%(bob)s" % {"bob": "bob"}
"%(bob)s" % {**{"bob": "bob"}}
"%(bob)s" % ["bob"] # F202
"%(bob)s" % ("bob",) # F202
"%(bob)s" % {"bob"} # F202
"%(bob)s" % [*["bob"]] # F202
"%(bob)s" % {"bob": "bob" for _ in range(1)}
"%(bob)s" % ["bob" for _ in range(1)] # F202
"%(bob)s" % ("bob" for _ in range(1)) # F202
"%(bob)s" % {"bob" for _ in range(1)} # F202

26
resources/test/fixtures/F503.py vendored Normal file
View File

@@ -0,0 +1,26 @@
dog = {"bob": "bob"}
# Single placeholder always fine
"%s" % dog
"%s" % {"bob": "bob"}
"%s" % {**{"bob": "bob"}}
"%s" % ["bob"]
"%s" % ("bob",)
"%s" % {"bob"}
"%s" % [*["bob"]]
"%s" % {"bob": "bob" for _ in range(1)}
"%s" % ["bob" for _ in range(1)]
"%s" % ("bob" for _ in range(1))
"%s" % {"bob" for _ in range(1)}
# Multiple placeholders
"%s %s" % dog
"%s %s" % {"bob": "bob"} # F503
"%s %s" % {**{"bob": "bob"}} # F503
"%s %s" % ["bob"]
"%s %s" % ("bob",)
"%s %s" % {"bob"}
"%s %s" % [*["bob"]]
"%s %s" % {"bob": "bob" for _ in range(1)} # F503
"%s %s" % ["bob" for _ in range(1)]
"%s %s" % ("bob" for _ in range(1))
"%s %s" % {"bob" for _ in range(1)}

6
resources/test/fixtures/F504.py vendored Normal file
View File

@@ -0,0 +1,6 @@
# Ruff has no way of knowing if the following are F505s
a = "wrong"
"%(a)s %(c)s" % {a: "?", "b": "!"} # F504 ("b" not used)
hidden = {"a": "!"}
"%(a)s %(c)s" % {"x": 1, **hidden} # Ok (cannot see through splat)

25
resources/test/fixtures/F50x.py vendored Normal file
View File

@@ -0,0 +1,25 @@
'%(foo)' % {'foo': 'bar'} # F501
'%s %(foo)s' % {'foo': 'bar'} # F506
'%(foo)s %s' % {'foo': 'bar'} # F506
'%j' % (1,) # F509
'%s %s' % (1,) # F507
'%s %s' % (1, 2, 3) # F507
'%(bar)s' % {} # F505
'%(bar)s' % {'bar': 1, 'baz': 2} # F504
'%(bar)s' % (1, 2, 3) # F502
'%s %s' % {'k': 'v'} # F503
'%(bar)*s' % {'bar': 'baz'} # F506, F508
# ok: single %s with mapping
'%s' % {'foo': 'bar', 'baz': 'womp'}
# ok: %% should not count towards placeholder count
'%% %s %% %s' % (1, 2)
# ok: * consumes one positional argument
'%.*f' % (2, 1.1234)
'%*.*f' % (5, 2, 3.1234)
# ok *args and **kwargs
a = []
'%s %s' % [*a]
'%s %s' % (*a,)
k = {}
'%(k)s' % {**k}

View File

@@ -38,5 +38,20 @@ def function(
def used(do):
return do
used("a", True)
used(do=True)
# Avoid FBT003 for explicitly allowed methods.
"""
FBT003 Boolean positional value on dict
"""
a = {"a": "b"}
a.get("hello", False)
{}.get("hello", False)
{}.setdefault("hello", True)
{}.pop("hello", False)
{}.pop(True, False)
dict.fromkeys(("world",), True)
{}.deploy(True, False)

View File

@@ -18,6 +18,9 @@ def f() -> None:
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Invalid (but external)
d = 1 # noqa: F841, V101
# fmt: off
# Invalid - no space before #
d = 1# noqa: E501

14
resources/test/fixtures/T100.py vendored Normal file
View File

@@ -0,0 +1,14 @@
breakpoint()
import pdb
from builtins import breakpoint
from pdb import set_trace as st
from celery.contrib.rdb import set_trace
from celery.contrib import rdb
import celery.contrib.rdb
breakpoint()
st()
set_trace()

View File

@@ -68,3 +68,13 @@ open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=N
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open = 1
open("foo", "U")
open("foo", "Ur")
open("foo", "Ub")
open("foo", "rUb")
open("foo", "r")
open("foo", "rt")
open("f", "r", encoding="UTF-8")
open("f", "wt")

View File

@@ -44,7 +44,7 @@ expectation.expected.add((
@expect("D407: Missing dashed underline after section ('Returns')",
arg_count=3)
@expect("D413: Missing blank line after last section ('Raises')", arg_count=3)
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None, **kwargs):
"""Fetches rows from a Bigtable.
Retrieves rows pertaining to the given keys from the Table instance
@@ -57,6 +57,7 @@ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.
**kwargs: More keyword arguments.
Returns:
A dict mapping keys to the corresponding table row data

View File

@@ -73,7 +73,7 @@ expectation.expected.add((
"(found 'A')", arg_count=3)
@expect("D413: Missing blank line after last section ('Examples')",
arg_count=3)
def foo(var1, var2, long_var_name='hi'):
def foo(var1, var2, long_var_name='hi', **kwargs):
r"""A one-line summary that does not use variable names.
Several sentences providing an extended description. Refer to
@@ -91,6 +91,8 @@ def foo(var1, var2, long_var_name='hi'):
detail, e.g. ``(N,) ndarray`` or ``array_like``.
long_var_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
**kwargs : int
More keyword arguments.
Returns
-------

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from models import (
Fruit,
@@ -28,3 +29,12 @@ class Foo:
@classmethod
def d(cls) -> Fruit:
return cls(x=0, y=0)
def f(x: int) -> List[int]:
y = List[int]()
y.append(x)
return y
x: Optional[int] = None

View File

@@ -5,6 +5,7 @@ extend-exclude = [
"migrations",
"with_excluded_file/other_excluded_file.py",
]
external = ["V101"]
per-file-ignores = { "__init__.py" = ["F401"] }
[tool.ruff.flake8-bugbear]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.140"
version = "0.0.149"
edition = "2021"
[dependencies]

View File

@@ -107,9 +107,3 @@ pub struct Binding {
/// the binding was last used.
pub used: Option<(usize, Range)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,
ImportFrom,
}

View File

@@ -4,14 +4,14 @@ use serde::{Deserialize, Serialize};
pub mod fixer;
pub mod helpers;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Patch {
pub content: String,
pub location: Location,
pub end_location: Location,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub patch: Patch,
}

View File

@@ -3,7 +3,6 @@
use std::collections::BTreeMap;
use std::path::Path;
use itertools::Itertools;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Withitem;
@@ -19,8 +18,7 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, ImportKind, Node, Range,
Scope, ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::{helpers, operations, visitor};
@@ -33,11 +31,13 @@ use crate::python::typing::SubscriptKind;
use crate::settings::types::PythonVersion;
use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator;
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print,
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, rules,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pyupgrade, rules,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -76,6 +76,7 @@ pub struct Checker<'a> {
in_f_string: Option<Range>,
in_annotation: bool,
in_deferred_string_annotation: bool,
in_deferred_annotation: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
@@ -122,6 +123,7 @@ impl<'a> Checker<'a> {
in_f_string: None,
in_annotation: false,
in_deferred_string_annotation: false,
in_deferred_annotation: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
@@ -590,7 +592,7 @@ where
self.binding_context(),
),
used: None,
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
} else {
@@ -625,16 +627,25 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
))
} else {
None
},
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) =
flake8_debugger::checks::debugger_import(stmt, None, &alias.node.name)
{
self.add_check(check);
}
}
if let Some(asname) = &alias.node.asname {
for alias in names {
if let Some(asname) = &alias.node.asname {
@@ -703,12 +714,7 @@ where
self.from_imports
.entry(module)
.or_insert_with(FxHashSet::default)
.extend(
names
.iter()
.filter(|alias| alias.node.asname.is_none())
.map(|alias| alias.node.name.as_str()),
);
.extend(names.iter().map(|alias| alias.node.name.as_str()));
}
for alias in names {
if let Some(asname) = &alias.node.asname {
@@ -746,9 +752,9 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
)),
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
@@ -760,7 +766,7 @@ where
if !ALL_FEATURE_NAMES.contains(&&*alias.node.name) {
self.add_check(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
Range::from_located(stmt),
Range::from_located(alias),
));
}
}
@@ -822,6 +828,7 @@ where
None => alias.node.name.to_string(),
Some(parent) => format!("{parent}.{}", alias.node.name),
};
let range = Range::from_located(alias);
self.add_binding(
name,
Binding {
@@ -844,12 +851,12 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
range,
))
} else {
None
},
range: Range::from_located(stmt),
range,
},
);
}
@@ -864,6 +871,17 @@ where
}
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) = flake8_debugger::checks::debugger_import(
stmt,
module.as_ref().map(String::as_str),
&alias.node.name,
) {
self.add_check(check);
}
}
if let Some(asname) = &alias.node.asname {
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
@@ -1174,10 +1192,14 @@ where
// Pre-visit.
match &expr.node {
ExprKind::Subscript { value, slice, .. } => {
// Ex) typing.List[...]
// Ex) Optional[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U007)
&& self.settings.target_version >= PythonVersion::Py310
&& (self.settings.target_version >= PythonVersion::Py310
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
{
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
}
@@ -1216,7 +1238,11 @@ where
// Ex) List[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(
expr,
&self.from_imports,
@@ -1251,8 +1277,12 @@ where
}
ExprKind::Attribute { attr, .. } => {
// Ex) typing.List[...]
if self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(expr, &self.from_imports, &self.import_aliases)
{
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
@@ -1621,7 +1651,7 @@ where
// flake8-boolean-trap
if self.settings.enabled.contains(&CheckCode::FBT003) {
flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call(
self, args,
self, args, func,
);
}
if let ExprKind::Name { id, ctx } = &func.node {
@@ -1634,6 +1664,18 @@ where
}
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) = flake8_debugger::checks::debugger_call(
expr,
func,
&self.from_imports,
&self.import_aliases,
) {
self.add_check(check);
}
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF101) {
rules::plugins::convert_exit_to_sys_exit(self, func);
@@ -1688,6 +1730,116 @@ where
pyflakes::plugins::invalid_print_syntax(self, left);
}
}
ExprKind::BinOp {
left,
op: Operator::Mod,
right,
} => {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &left.node
{
if self.settings.enabled.contains(&CheckCode::F501)
|| self.settings.enabled.contains(&CheckCode::F502)
|| self.settings.enabled.contains(&CheckCode::F503)
|| self.settings.enabled.contains(&CheckCode::F504)
|| self.settings.enabled.contains(&CheckCode::F505)
|| self.settings.enabled.contains(&CheckCode::F506)
|| self.settings.enabled.contains(&CheckCode::F507)
|| self.settings.enabled.contains(&CheckCode::F508)
|| self.settings.enabled.contains(&CheckCode::F509)
{
let location = Range::from_located(expr);
match pyflakes::cformat::CFormatSummary::try_from(value.as_ref()) {
Err(CFormatError {
typ: CFormatErrorType::UnsupportedFormatChar(c),
..
}) => {
if self.settings.enabled.contains(&CheckCode::F509) {
self.add_check(Check::new(
CheckKind::PercentFormatUnsupportedFormatCharacter(c),
location,
));
}
}
Err(e) => {
if self.settings.enabled.contains(&CheckCode::F501) {
self.add_check(Check::new(
CheckKind::PercentFormatInvalidFormat(e.to_string()),
location,
));
}
}
Ok(summary) => {
if self.settings.enabled.contains(&CheckCode::F502) {
if let Some(check) =
pyflakes::checks::percent_format_expected_mapping(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F503) {
if let Some(check) =
pyflakes::checks::percent_format_expected_sequence(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F504) {
if let Some(check) =
pyflakes::checks::percent_format_extra_named_arguments(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F505) {
if let Some(check) =
pyflakes::checks::percent_format_missing_arguments(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F506) {
if let Some(check) =
pyflakes::checks::percent_format_mixed_positional_and_named(
&summary, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F507) {
if let Some(check) =
pyflakes::checks::percent_format_positional_count_mismatch(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F508) {
if let Some(check) =
pyflakes::checks::percent_format_star_requires_sequence(
&summary, right, location,
)
{
self.add_check(check);
}
}
}
}
}
}
}
ExprKind::UnaryOp { op, operand } => {
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
@@ -2604,7 +2756,9 @@ impl<'a> Checker<'a> {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_annotation = true;
self.visit_expr(expr);
self.in_deferred_annotation = false;
}
}
@@ -2630,9 +2784,9 @@ impl<'a> Checker<'a> {
}
}
for (expr, (scopes, parents)) in allocator.iter().zip(stacks) {
self.in_deferred_string_annotation = true;
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_string_annotation = true;
self.visit_expr(expr);
self.in_deferred_string_annotation = false;
}
@@ -2763,68 +2917,54 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::F401) {
// Collect all unused imports by location. (Multiple unused imports at the same
// location indicates an `import from`.)
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
type UnusedImport<'a> = (&'a String, &'a Range);
let mut unused: BTreeMap<(usize, Option<usize>), Vec<UnusedImport>> =
BTreeMap::new();
for (name, binding) in &scope.values {
if !matches!(
binding.kind,
BindingKind::Importation(..)
| BindingKind::SubmoduleImportation(..)
| BindingKind::FromImportation(..)
) {
continue;
}
let (full_name, context) = match &binding.kind {
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context) => {
(full_name, context)
}
_ => continue,
};
let used = binding.used.is_some()
// Skip used exports from `__all__`
if binding.used.is_some()
|| all_names
.as_ref()
.map(|names| names.contains(name))
.unwrap_or_default();
if !used {
match &binding.kind {
BindingKind::FromImportation(_, full_name, context) => {
unused
.entry((
ImportKind::ImportFrom,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context) => {
unused
.entry((
ImportKind::Import,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
_ => unreachable!("Already filtered on BindingKind."),
}
.unwrap_or_default()
{
continue;
}
unused
.entry((context.defined_by, context.defined_in))
.or_default()
.push((full_name, &binding.range));
}
for ((kind, defined_by, defined_in), full_names) in unused {
for ((defined_by, defined_in), unused_imports) in unused {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if self.patch(&CheckCode::F401) {
let in_init_py = self.path.ends_with("__init__.py");
let fix = if !in_init_py && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match match kind {
ImportKind::Import => pyflakes::fixes::remove_unused_imports,
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
}(
self.locator, &full_names, child, parent, &deleted
match pyflakes::fixes::remove_unused_imports(
self.locator,
&unused_imports,
child,
parent,
&deleted,
) {
Ok(fix) => {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
@@ -2841,24 +2981,13 @@ impl<'a> Checker<'a> {
None
};
if self.path.ends_with("__init__.py") {
checks.push(Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
true,
),
Range::from_located(child),
));
} else {
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
false,
),
Range::from_located(child),
CheckKind::UnusedImport(full_name.clone(), in_init_py),
*range,
);
if let Some(fix) = fix {
check.amend(fix);
if let Some(fix) = fix.as_ref() {
check.amend(fix.clone());
}
checks.push(check);
}

View File

@@ -14,15 +14,18 @@ use crate::settings::Settings;
// Regex from PEP263
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").expect("Invalid regex"));
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").unwrap());
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap());
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
let mut chunks = line.split_whitespace();
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
// Do not enforce the line length for commented lines with a single word
!(first == "#" && chunks.next().is_none())
// Do not enforce the line length for commented lines that end with a URL
// or contain only a single word.
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
} else {
// Single word / no printable chars - no way to make the line shorter
false
@@ -209,7 +212,7 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
if matches.contains(&code) {
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());

View File

@@ -6,7 +6,7 @@ use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, rules, Settings};
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
pub fn check_tokens(
locator: &SourceCodeLocator,
@@ -23,6 +23,7 @@ pub fn check_tokens(
|| settings.enabled.contains(&CheckCode::Q001)
|| settings.enabled.contains(&CheckCode::Q002)
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_commented_out_code = settings.enabled.contains(&CheckCode::ERA001);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let mut state_machine = StateMachine::default();
@@ -72,6 +73,17 @@ pub fn check_tokens(
}
}
// eradicate
if enforce_commented_out_code {
if matches!(tok, Tok::Comment) {
if let Some(check) =
eradicate::checks::commented_out_code(locator, start, end, settings, autofix)
{
checks.push(check);
}
}
}
// W605
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {

View File

@@ -3,10 +3,11 @@ use std::str::FromStr;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter, EnumString};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::flake8_debugger::types::DebuggerUsingType;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::pyupgrade::types::Primitive;
@@ -16,6 +17,7 @@ use crate::pyupgrade::types::Primitive;
EnumIter,
EnumString,
Debug,
Display,
PartialEq,
Eq,
Clone,
@@ -52,6 +54,15 @@ pub enum CheckCode {
F405,
F406,
F407,
F501,
F502,
F503,
F504,
F505,
F506,
F507,
F508,
F509,
F521,
F522,
F523,
@@ -129,6 +140,8 @@ pub enum CheckCode {
C415,
C416,
C417,
// flake8-debugger
T100,
// mccabe
C901,
// flake8-tidy-imports
@@ -242,6 +255,8 @@ pub enum CheckCode {
N818,
// isort
I001,
// eradicate
ERA001,
// flake8-bandit
S101,
S102,
@@ -270,8 +285,10 @@ pub enum CheckCategory {
Pydocstyle,
Pyupgrade,
PEP8Naming,
Eradicate,
Flake8Bandit,
Flake8Comprehensions,
Flake8Debugger,
Flake8BooleanTrap,
Flake8Bugbear,
Flake8Builtins,
@@ -289,65 +306,71 @@ pub enum CheckCategory {
impl CheckCategory {
pub fn title(&self) -> &'static str {
match self {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Isort => "isort",
CheckCategory::Eradicate => "eradicate",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake8Bandit => "flake8-bandit",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Flake8BooleanTrap => "flake8-boolean-trap",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
CheckCategory::Ruff => "Ruff-specific rules",
CheckCategory::Meta => "Meta rules",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
}
pub fn url(&self) -> Option<&'static str> {
match self {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Eradicate => Some("https://pypi.org/project/eradicate/2.1.0/"),
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"),
CheckCategory::Flake8BlindExcept => {
Some("https://pypi.org/project/flake8-blind-except/0.2.1/")
}
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
}
CheckCategory::Ruff => None,
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8Debugger => {
Some("https://pypi.org/project/flake8-debugger/4.1.2/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
CheckCategory::Meta => None,
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Ruff => None,
}
}
}
@@ -407,6 +430,15 @@ pub enum CheckKind {
LateFutureImport,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
PercentFormatExpectedMapping,
PercentFormatExpectedSequence,
PercentFormatExtraNamedArguments(Vec<String>),
PercentFormatInvalidFormat(String),
PercentFormatMissingArgument(Vec<String>),
PercentFormatMixedPositionalAndNamed,
PercentFormatPositionalCountMismatch(usize, usize),
PercentFormatStarRequiresSequence,
PercentFormatUnsupportedFormatCharacter(char),
RaiseNotImplemented,
ReturnOutsideFunction,
StringDotFormatExtraNamedArguments(Vec<String>),
@@ -418,7 +450,7 @@ pub enum CheckKind {
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>, bool),
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction,
// flake8-builtins
@@ -472,6 +504,8 @@ pub enum CheckKind {
UnnecessarySubscriptReversal(String),
UnnecessaryComprehension(String),
UnnecessaryMap(String),
// flake8-debugger
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedRelativeImport(Strictness),
// flake8-print
@@ -583,6 +617,8 @@ pub enum CheckKind {
ErrorSuffixOnExceptionName(String),
// isort
UnsortedImports,
// eradicate
CommentedOutCode,
// flake8-bandit
AssertUsed,
ExecUsed,
@@ -613,7 +649,8 @@ impl CheckCode {
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
&LintSource::Lines
}
CheckCode::Q000
CheckCode::ERA001
| CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
| CheckCode::Q003
@@ -649,7 +686,7 @@ impl CheckCode {
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()], false),
CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
@@ -658,6 +695,15 @@ impl CheckCode {
}
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F501 => CheckKind::PercentFormatInvalidFormat("...".to_string()),
CheckCode::F502 => CheckKind::PercentFormatExpectedMapping,
CheckCode::F503 => CheckKind::PercentFormatExpectedSequence,
CheckCode::F504 => CheckKind::PercentFormatExtraNamedArguments(vec!["...".to_string()]),
CheckCode::F505 => CheckKind::PercentFormatMissingArgument(vec!["...".to_string()]),
CheckCode::F506 => CheckKind::PercentFormatMixedPositionalAndNamed,
CheckCode::F507 => CheckKind::PercentFormatPositionalCountMismatch(4, 2),
CheckCode::F508 => CheckKind::PercentFormatStarRequiresSequence,
CheckCode::F509 => CheckKind::PercentFormatUnsupportedFormatCharacter('c'),
CheckCode::F521 => CheckKind::StringDotFormatInvalidFormat("...".to_string()),
CheckCode::F522 => {
CheckKind::StringDotFormatExtraNamedArguments(vec!["...".to_string()])
@@ -754,6 +800,8 @@ impl CheckCode {
}
CheckCode::C416 => CheckKind::UnnecessaryComprehension("(list|set)".to_string()),
CheckCode::C417 => CheckKind::UnnecessaryMap("(list|set|dict)".to_string()),
// flake8-debugger
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-print
@@ -886,6 +934,8 @@ impl CheckCode {
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
// isort
CheckCode::I001 => CheckKind::UnsortedImports,
// eradicate
CheckCode::ERA001 => CheckKind::CommentedOutCode,
// flake8-bandit
CheckCode::S101 => CheckKind::AssertUsed,
CheckCode::S102 => CheckKind::ExecUsed,
@@ -911,58 +961,20 @@ impl CheckCode {
pub fn category(&self) -> CheckCategory {
#[allow(clippy::match_same_arms)]
match self {
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::A001 => CheckCategory::Flake8Builtins,
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
@@ -1007,48 +1019,7 @@ impl CheckCode {
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
@@ -1093,6 +1064,69 @@ impl CheckCode {
CheckCode::D417 => CheckCategory::Pydocstyle,
CheckCode::D418 => CheckCategory::Pydocstyle,
CheckCode::D419 => CheckCategory::Pydocstyle,
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::ERA001 => CheckCategory::Eradicate,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F501 => CheckCategory::Pyflakes,
CheckCode::F502 => CheckCategory::Pyflakes,
CheckCode::F503 => CheckCategory::Pyflakes,
CheckCode::F504 => CheckCategory::Pyflakes,
CheckCode::F505 => CheckCategory::Pyflakes,
CheckCode::F506 => CheckCategory::Pyflakes,
CheckCode::F507 => CheckCategory::Pyflakes,
CheckCode::F508 => CheckCategory::Pyflakes,
CheckCode::F509 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
CheckCode::N803 => CheckCategory::PEP8Naming,
@@ -1108,22 +1142,49 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
CheckCode::S104 => CheckCategory::Flake8Bandit,
CheckCode::S105 => CheckCategory::Flake8Bandit,
CheckCode::S106 => CheckCategory::Flake8Bandit,
CheckCode::S107 => CheckCategory::Flake8Bandit,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::T100 => CheckCategory::Flake8Debugger,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
}
}
}
@@ -1162,6 +1223,15 @@ impl CheckKind {
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::PercentFormatExpectedMapping => &CheckCode::F502,
CheckKind::PercentFormatExpectedSequence => &CheckCode::F503,
CheckKind::PercentFormatExtraNamedArguments(_) => &CheckCode::F504,
CheckKind::PercentFormatInvalidFormat(_) => &CheckCode::F501,
CheckKind::PercentFormatMissingArgument(_) => &CheckCode::F505,
CheckKind::PercentFormatMixedPositionalAndNamed => &CheckCode::F506,
CheckKind::PercentFormatPositionalCountMismatch(..) => &CheckCode::F507,
CheckKind::PercentFormatStarRequiresSequence => &CheckCode::F508,
CheckKind::PercentFormatUnsupportedFormatCharacter(_) => &CheckCode::F509,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::StringDotFormatExtraNamedArguments(_) => &CheckCode::F522,
@@ -1234,6 +1304,8 @@ impl CheckKind {
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
// flake8-debugger
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
// flake8-print
@@ -1345,6 +1417,8 @@ impl CheckKind {
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
// isort
CheckKind::UnsortedImports => &CheckCode::I001,
// eradicate
CheckKind::CommentedOutCode => &CheckCode::ERA001,
// flake8-bandit
CheckKind::AssertUsed => &CheckCode::S101,
CheckKind::ExecUsed => &CheckCode::S102,
@@ -1450,6 +1524,35 @@ impl CheckKind {
},
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
CheckKind::PercentFormatInvalidFormat(message) => {
format!("'...' % ... has invalid format string: {message}")
}
CheckKind::PercentFormatUnsupportedFormatCharacter(char) => {
format!("'...' % ... has unsupported format character '{char}'")
}
CheckKind::PercentFormatExpectedMapping => {
"'...' % ... expected mapping but got sequence".to_string()
}
CheckKind::PercentFormatExpectedSequence => {
"'...' % ... expected sequence but got mapping".to_string()
}
CheckKind::PercentFormatExtraNamedArguments(missing) => {
let message = missing.join(", ");
format!("'...' % ... has unused named argument(s): {message}")
}
CheckKind::PercentFormatMissingArgument(missing) => {
let message = missing.join(", ");
format!("'...' % ... is missing argument(s) for placeholder(s): {message}")
}
CheckKind::PercentFormatMixedPositionalAndNamed => {
"'...' % ... has mixed positional and named placeholders".to_string()
}
CheckKind::PercentFormatPositionalCountMismatch(wanted, got) => {
format!("'...' % ... has {wanted} placeholder(s) but {got} substitution(s)")
}
CheckKind::PercentFormatStarRequiresSequence => {
"'...' % ... `*` specifier requires sequence".to_string()
}
CheckKind::RaiseNotImplemented => {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
}
@@ -1501,12 +1604,11 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(names, in_init_py) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
CheckKind::UnusedImport(name, in_init_py) => {
if *in_init_py {
format!("{names} imported but unused and missing from `__all__`")
format!("`{name}` imported but unused and missing from `__all__`")
} else {
format!("{names} imported but unused")
format!("`{name}` imported but unused")
}
}
CheckKind::UnusedVariable(name) => {
@@ -1718,6 +1820,11 @@ impl CheckKind {
format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)")
}
}
// flake8-debugger
CheckKind::Debugger(using_type) => match using_type {
DebuggerUsingType::Call(name) => format!("Trace found: `{name}` used"),
DebuggerUsingType::Import(name) => format!("Import for `{name}` found"),
},
// flake8-tidy-imports
CheckKind::BannedRelativeImport(strictness) => match strictness {
Strictness::Parents => {
@@ -2023,6 +2130,8 @@ impl CheckKind {
}
// isort
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
// eradicate
CheckKind::CommentedOutCode => "Found commented-out code".to_string(),
// flake8-bandit
CheckKind::AssertUsed => "Use of `assert` detected".to_string(),
CheckKind::ExecUsed => "Use of `exec` detected".to_string(),
@@ -2127,6 +2236,7 @@ impl CheckKind {
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)

View File

@@ -176,6 +176,10 @@ pub enum CheckCodePrefix {
E902,
E99,
E999,
ERA,
ERA0,
ERA00,
ERA001,
F,
F4,
F40,
@@ -187,6 +191,16 @@ pub enum CheckCodePrefix {
F406,
F407,
F5,
F50,
F501,
F502,
F503,
F504,
F505,
F506,
F507,
F508,
F509,
F52,
F521,
F522,
@@ -290,6 +304,9 @@ pub enum CheckCodePrefix {
S106,
S107,
T,
T1,
T10,
T100,
T2,
T20,
T201,
@@ -850,6 +867,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => vec![CheckCode::E902],
CheckCodePrefix::E99 => vec![CheckCode::E999],
CheckCodePrefix::E999 => vec![CheckCode::E999],
CheckCodePrefix::ERA => vec![CheckCode::ERA001],
CheckCodePrefix::ERA0 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA00 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA001 => vec![CheckCode::ERA001],
CheckCodePrefix::F => vec![
CheckCode::F401,
CheckCode::F402,
@@ -858,6 +879,15 @@ impl CheckCodePrefix {
CheckCode::F405,
CheckCode::F406,
CheckCode::F407,
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
@@ -911,6 +941,15 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => vec![CheckCode::F406],
CheckCodePrefix::F407 => vec![CheckCode::F407],
CheckCodePrefix::F5 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
@@ -918,6 +957,26 @@ impl CheckCodePrefix {
CheckCode::F525,
CheckCode::F541,
],
CheckCodePrefix::F50 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
],
CheckCodePrefix::F501 => vec![CheckCode::F501],
CheckCodePrefix::F502 => vec![CheckCode::F502],
CheckCodePrefix::F503 => vec![CheckCode::F503],
CheckCodePrefix::F504 => vec![CheckCode::F504],
CheckCodePrefix::F505 => vec![CheckCode::F505],
CheckCodePrefix::F506 => vec![CheckCode::F506],
CheckCodePrefix::F507 => vec![CheckCode::F507],
CheckCodePrefix::F508 => vec![CheckCode::F508],
CheckCodePrefix::F509 => vec![CheckCode::F509],
CheckCodePrefix::F52 => vec![
CheckCode::F521,
CheckCode::F522,
@@ -1149,7 +1208,10 @@ impl CheckCodePrefix {
CheckCodePrefix::S105 => vec![CheckCode::S105],
CheckCodePrefix::S106 => vec![CheckCode::S106],
CheckCodePrefix::S107 => vec![CheckCode::S107],
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T1 => vec![CheckCode::T100],
CheckCodePrefix::T10 => vec![CheckCode::T100],
CheckCodePrefix::T100 => vec![CheckCode::T100],
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
@@ -1440,6 +1502,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => PrefixSpecificity::Explicit,
CheckCodePrefix::E99 => PrefixSpecificity::Tens,
CheckCodePrefix::E999 => PrefixSpecificity::Explicit,
CheckCodePrefix::ERA => PrefixSpecificity::Category,
CheckCodePrefix::ERA0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ERA00 => PrefixSpecificity::Tens,
CheckCodePrefix::ERA001 => PrefixSpecificity::Explicit,
CheckCodePrefix::F => PrefixSpecificity::Category,
CheckCodePrefix::F4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F40 => PrefixSpecificity::Tens,
@@ -1451,6 +1517,16 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => PrefixSpecificity::Explicit,
CheckCodePrefix::F407 => PrefixSpecificity::Explicit,
CheckCodePrefix::F5 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F50 => PrefixSpecificity::Tens,
CheckCodePrefix::F501 => PrefixSpecificity::Explicit,
CheckCodePrefix::F502 => PrefixSpecificity::Explicit,
CheckCodePrefix::F503 => PrefixSpecificity::Explicit,
CheckCodePrefix::F504 => PrefixSpecificity::Explicit,
CheckCodePrefix::F505 => PrefixSpecificity::Explicit,
CheckCodePrefix::F506 => PrefixSpecificity::Explicit,
CheckCodePrefix::F507 => PrefixSpecificity::Explicit,
CheckCodePrefix::F508 => PrefixSpecificity::Explicit,
CheckCodePrefix::F509 => PrefixSpecificity::Explicit,
CheckCodePrefix::F52 => PrefixSpecificity::Tens,
CheckCodePrefix::F521 => PrefixSpecificity::Explicit,
CheckCodePrefix::F522 => PrefixSpecificity::Explicit,
@@ -1554,6 +1630,9 @@ impl CheckCodePrefix {
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
CheckCodePrefix::T => PrefixSpecificity::Category,
CheckCodePrefix::T1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T10 => PrefixSpecificity::Tens,
CheckCodePrefix::T100 => PrefixSpecificity::Explicit,
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
CheckCodePrefix::T201 => PrefixSpecificity::Explicit,
@@ -1612,6 +1691,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::ERA,
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,

View File

@@ -7,15 +7,16 @@ use rustc_hash::FxHashMap;
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::types::{FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion};
use crate::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
};
#[derive(Debug, Parser)]
#[command(author, about = "Ruff: An extremely fast Python linter.")]
#[command(version)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(required = true)]
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
pub files: Vec<PathBuf>,
/// Path to the `pyproject.toml` file to use for configuration.
#[arg(long)]
@@ -77,8 +78,8 @@ pub struct Cli {
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t = SerializationFormat::Text)]
pub format: SerializationFormat,
#[arg(long, value_enum)]
pub format: Option<SerializationFormat>,
/// Show violations with source code.
#[arg(long)]
pub show_source: bool,
@@ -114,6 +115,9 @@ pub struct Cli {
/// Explain a rule.
#[arg(long)]
pub explain: Option<CheckCode>,
/// Generate shell completion
#[arg(long, hide = true, value_name = "SHELL")]
pub generate_shell_completion: Option<clap_complete_command::Shell>,
}
impl Cli {
@@ -140,8 +144,6 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
LogLevel::Quiet
} else if cli.verbose {
LogLevel::Verbose
} else if matches!(cli.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
LogLevel::Default
}

View File

@@ -1,12 +1,12 @@
use std::path::PathBuf;
use anyhow::Result;
use anyhow::{bail, Result};
use serde::Serialize;
use walkdir::DirEntry;
use crate::checks::CheckCode;
use crate::fs::iter_python_files;
use crate::printer::SerializationFormat;
use crate::settings::types::SerializationFormat;
use crate::{Configuration, Settings};
/// Print the user-facing configuration settings.
@@ -43,7 +43,7 @@ struct Explanation<'a> {
/// Explain a `CheckCode` to the user.
pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
match format {
SerializationFormat::Text => {
SerializationFormat::Text | SerializationFormat::Grouped => {
println!(
"{} ({}): {}",
code.as_ref(),
@@ -61,6 +61,9 @@ pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
})?
);
}
SerializationFormat::Junit => {
bail!("`--explain` does not support junit format")
}
};
Ok(())
}

51
src/eradicate/checks.rs Normal file
View File

@@ -0,0 +1,51 @@
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{CheckCode, CheckKind};
use crate::eradicate::detection::comment_contains_code;
use crate::{Check, Settings, SourceCodeLocator};
fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
} else if !char.is_whitespace() {
return false;
}
}
unreachable!("Comment should contain '#' character")
}
/// ERA001
pub fn commented_out_code(
locator: &SourceCodeLocator,
start: Location,
end: Location,
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
let line = locator.slice_source_code_range(&Range {
location,
end_location,
});
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(&line) && comment_contains_code(&line) {
let mut check = Check::new(
CheckKind::CommentedOutCode,
Range {
location: start,
end_location: end,
},
);
if autofix && settings.fixable.contains(&CheckCode::ERA001) {
check.amend(Fix::deletion(location, end_location));
}
Some(check)
} else {
None
}
}

223
src/eradicate/detection.rs Normal file
View File

@@ -0,0 +1,223 @@
/// See: [eradicate.py](https://github.com/myint/eradicate/blob/98f199940979c94447a461d50d27862b118b282d/eradicate.py)
use once_cell::sync::Lazy;
use regex::Regex;
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX"
).unwrap()
});
static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap());
static CODE_INDICATORS: &[&str] = &[
"(", ")", "[", "]", "{", "}", ":", "=", "%", "print", "return", "break", "continue", "import",
];
static CODE_KEYWORDS: Lazy<Vec<Regex>> = Lazy::new(|| {
vec![
Regex::new(r"^\s*elif\s+.*\s*:\s*$").unwrap(),
Regex::new(r"^\s*else\s*:\s*$").unwrap(),
Regex::new(r"^\s*try\s*:\s*$").unwrap(),
Regex::new(r"^\s*finally\s*:\s*$").unwrap(),
Regex::new(r"^\s*except\s+.*\s*:\s*$").unwrap(),
]
});
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());
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());
/// Returns `true` if a comment contains Python code.
pub fn comment_contains_code(line: &str) -> bool {
let line = if let Some(line) = line.trim().strip_prefix('#') {
line.trim()
} else {
return false;
};
// Ignore non-comment related hashes (e.g., "# Issue #999").
if HASH_NUMBER.is_match(line) {
return false;
}
// Ignore whitelisted comments.
if ALLOWLIST_REGEX.is_match(line) {
return false;
}
if CODING_COMMENT_REGEX.is_match(line) {
return false;
}
// Check that this is possibly code.
if CODE_INDICATORS.iter().all(|symbol| !line.contains(symbol)) {
return false;
}
if multiline_case(line) {
return true;
}
if CODE_KEYWORDS.iter().any(|symbol| symbol.is_match(line)) {
return true;
}
let line = PRINT_RETURN_REGEX.replace_all(line, "");
if PARTIAL_DICTIONARY_REGEX.is_match(&line) {
return true;
}
// Finally, compile the source code.
rustpython_parser::parser::parse_program(&line, "<filename>").is_ok()
}
/// Returns `true` if a line is probably part of some multiline code.
fn multiline_case(line: &str) -> bool {
if line.ends_with('\\') {
return true;
}
if MULTILINE_ASSIGNMENT_REGEX.is_match(line) {
return true;
}
if BRACKET_REGEX.is_match(line) {
return true;
}
false
}
#[cfg(test)]
mod tests {
use crate::eradicate::detection::comment_contains_code;
#[test]
fn comment_contains_code_basic() {
assert!(comment_contains_code("# x = 1"));
assert!(comment_contains_code("#from foo import eradicate"));
assert!(comment_contains_code("#import eradicate"));
assert!(comment_contains_code(r#"#"key": value,"#));
assert!(comment_contains_code(r#"#"key": "value","#));
assert!(comment_contains_code(r#"#"key": 1 + 1,"#));
assert!(comment_contains_code("#'key': 1 + 1,"));
assert!(comment_contains_code(r#"#"key": {"#));
assert!(comment_contains_code("#}"));
assert!(comment_contains_code("#} )]"));
assert!(!comment_contains_code("#"));
assert!(!comment_contains_code("# This is a (real) comment."));
assert!(!comment_contains_code("# 123"));
assert!(!comment_contains_code("# 123.1"));
assert!(!comment_contains_code("# 1, 2, 3"));
assert!(!comment_contains_code("x = 1 # x = 1"));
assert!(!comment_contains_code(
"# pylint: disable=redefined-outer-name"
));
assert!(!comment_contains_code("# Issue #999: This is not code"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#},"));
}
#[test]
fn comment_contains_code_with_print() {
assert!(comment_contains_code("#print"));
assert!(comment_contains_code("#print(1)"));
assert!(comment_contains_code("#print 1"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_return() {
assert!(comment_contains_code("#return x"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_multiline() {
assert!(comment_contains_code("#else:"));
assert!(comment_contains_code("# else : "));
assert!(comment_contains_code(r#"# "foo %d" % \\"#));
assert!(comment_contains_code("#elif True:"));
assert!(comment_contains_code("#x = foo("));
assert!(comment_contains_code("#except Exception:"));
assert!(!comment_contains_code("# this is = to that :("));
assert!(!comment_contains_code("#else"));
assert!(!comment_contains_code("#or else:"));
assert!(!comment_contains_code("#else True:"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():"));
}
#[test]
fn comment_contains_code_with_sentences() {
assert!(!comment_contains_code("#code is good"));
}
#[test]
fn comment_contains_code_with_encoding() {
assert!(comment_contains_code("# codings=utf-8"));
assert!(!comment_contains_code("# coding=utf-8"));
assert!(!comment_contains_code("#coding= utf-8"));
assert!(!comment_contains_code("# coding: utf-8"));
assert!(!comment_contains_code("# encoding: utf8"));
}
#[test]
fn comment_contains_code_with_default_allowlist() {
assert!(!comment_contains_code("# pylint: disable=A0123"));
assert!(!comment_contains_code("# pylint:disable=A0123"));
assert!(!comment_contains_code("# pylint: disable = A0123"));
assert!(!comment_contains_code("# pylint:disable = A0123"));
assert!(!comment_contains_code("# pyright: reportErrorName=true"));
assert!(!comment_contains_code("# noqa"));
assert!(!comment_contains_code("# NOQA"));
assert!(!comment_contains_code("# noqa: A123"));
assert!(!comment_contains_code("# noqa:A123"));
assert!(!comment_contains_code("# nosec"));
assert!(!comment_contains_code("# fmt: on"));
assert!(!comment_contains_code("# fmt: off"));
assert!(!comment_contains_code("# fmt:on"));
assert!(!comment_contains_code("# fmt:off"));
assert!(!comment_contains_code("# isort: on"));
assert!(!comment_contains_code("# isort:on"));
assert!(!comment_contains_code("# isort: off"));
assert!(!comment_contains_code("# isort:off"));
assert!(!comment_contains_code("# isort: skip"));
assert!(!comment_contains_code("# isort:skip"));
assert!(!comment_contains_code("# isort: skip_file"));
assert!(!comment_contains_code("# isort:skip_file"));
assert!(!comment_contains_code("# isort: split"));
assert!(!comment_contains_code("# isort:split"));
assert!(!comment_contains_code("# isort: dont-add-imports"));
assert!(!comment_contains_code("# isort:dont-add-imports"));
assert!(!comment_contains_code(
"# isort: dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort: dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code("# type: ignore"));
assert!(!comment_contains_code("# type:ignore"));
assert!(!comment_contains_code("# type: ignore[import]"));
assert!(!comment_contains_code("# type:ignore[import]"));
assert!(!comment_contains_code("# TODO: Do that"));
assert!(!comment_contains_code("# FIXME: Fix that"));
assert!(!comment_contains_code("# XXX: What ever"));
}
}

2
src/eradicate/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod detection;

View File

@@ -5,6 +5,19 @@ use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
/// Returns `true` if an argument is allowed to use a boolean trap. To return
/// `true`, the function name must be explicitly allowed, and the argument must
/// be either the first or second argument in the call.
fn allow_boolean_trap(func: &Expr) -> bool {
if let ExprKind::Attribute { attr, .. } = &func.node {
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
} else {
false
}
}
fn is_boolean_arg(arg: &Expr) -> bool {
matches!(
&arg.node,
@@ -60,8 +73,15 @@ pub fn check_boolean_default_value_in_function_definition(
}
}
pub fn check_boolean_positional_value_in_function_call(checker: &mut Checker, args: &[Expr]) {
for arg in args {
pub fn check_boolean_positional_value_in_function_call(
checker: &mut Checker,
args: &[Expr],
func: &Expr,
) {
for (index, arg) in args.iter().enumerate() {
if index < 2 && allow_boolean_trap(func) {
continue;
}
add_if_boolean(
checker,
arg,

View File

@@ -1,4 +1,4 @@
//! Settings for the `pep8-naming` plugin.
//! Settings for the `flake8-bugbear` plugin.
use serde::{Deserialize, Serialize};

View File

@@ -39,9 +39,7 @@ pub fn fix_unnecessary_generator_list(
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
let Expression::GeneratorExp(generator_exp) = &arg.value else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
@@ -82,9 +80,7 @@ pub fn fix_unnecessary_generator_set(
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
let Expression::GeneratorExp(generator_exp) = &arg.value else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
@@ -126,28 +122,20 @@ pub fn fix_unnecessary_generator_dict(
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 {
let Expression::GeneratorExp(generator_exp) = &arg.value 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 {
let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() 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 {
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) 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 {
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the second element"
));
@@ -192,9 +180,7 @@ pub fn fix_unnecessary_list_comprehension_set(
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
let Expression::ListComp(list_comp) = &arg.value else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
@@ -233,25 +219,18 @@ pub fn fix_unnecessary_list_comprehension_dict(
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
let Expression::ListComp(list_comp) = &arg.value else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
let tuple = if let Expression::Tuple(tuple) = &*list_comp.elt {
tuple
} else {
let Expression::Tuple(tuple) = &*list_comp.elt else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
};
let (key, comma, value) = match &tuple.elements[..] {
[Element::Simple {
let [Element::Simple {
value: key,
comma: Some(comma),
}, Element::Simple { value, .. }] => (key, comma, value),
_ => return Err(anyhow::anyhow!("Expected tuple with two elements")),
};
}, Element::Simple { value, .. }] = &tuple.elements[..] else { return Err(anyhow::anyhow!("Expected tuple with two elements")) };
body.value = Expression::DictComp(Box::new(DictComp {
key: Box::new(key.clone()),
@@ -409,9 +388,7 @@ pub fn fix_unnecessary_collection_call(
let mut tree = match_module(&module_text)?;
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 {
let Expression::Name(name) = &call.func.as_ref() else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Name"));
};

View File

@@ -0,0 +1,64 @@
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Expr, Stmt};
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_debugger::types::DebuggerUsingType;
const DEBUGGERS: &[(&str, &str)] = &[
("pdb", "set_trace"),
("pudb", "set_trace"),
("ipdb", "set_trace"),
("ipdb", "sset_trace"),
("IPython.terminal.embed", "InteractiveShellEmbed"),
("IPython.frontend.terminal.embed", "InteractiveShellEmbed"),
("celery.contrib.rdb", "set_trace"),
("builtins", "breakpoint"),
("", "breakpoint"),
];
/// Checks for the presence of a debugger call.
pub fn debugger_call(
expr: &Expr,
func: &Expr,
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
) -> Option<Check> {
let call_path = dealias_call_path(collect_call_paths(func), import_aliases);
if DEBUGGERS
.iter()
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
{
Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Call(call_path.join("."))),
Range::from_located(expr),
))
} else {
None
}
}
/// Checks for the presence of a debugger import.
pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Check> {
if let Some(module) = module {
if let Some((module_name, member)) = DEBUGGERS
.iter()
.find(|(module_name, member)| module_name == &module && member == &name)
{
return Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Import(format!("{module_name}.{member}"))),
Range::from_located(stmt),
));
}
} else if DEBUGGERS
.iter()
.any(|(module_name, ..)| module_name == &name)
{
return Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Import(name.to_string())),
Range::from_located(stmt),
));
}
None
}

View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod types;

View File

@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DebuggerUsingType {
Call(String),
Import(String),
}

View File

@@ -39,6 +39,7 @@ pub mod commands;
mod cst;
mod directives;
mod docstrings;
mod eradicate;
mod flake8_2020;
pub mod flake8_annotations;
pub mod flake8_bandit;
@@ -47,6 +48,7 @@ pub mod flake8_boolean_trap;
pub mod flake8_bugbear;
mod flake8_builtins;
mod flake8_comprehensions;
mod flake8_debugger;
mod flake8_print;
pub mod flake8_quotes;
pub mod flake8_tidy_imports;

View File

@@ -7,6 +7,7 @@ use std::path::Path;
use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use log::debug;
use nohash_hasher::IntMap;
use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
@@ -46,6 +47,7 @@ impl AddAssign for Diagnostics {
/// Generate a list of `Check` violations from the source code contents at the
/// given `Path`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn check_path(
path: &Path,
contents: &str,
@@ -252,7 +254,7 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
tokens,
&locator,
&Directives {
noqa_line_for: Default::default(),
noqa_line_for: IntMap::default(),
isort_exclusions: directives.isort_exclusions,
},
settings,
@@ -260,7 +262,13 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
true,
)?;
add_noqa(&checks, &contents, &directives.noqa_line_for, path)
add_noqa(
path,
&checks,
&contents,
&directives.noqa_line_for,
&settings.external,
)
}
/// Apply autoformatting to the source code at the given `Path`.
@@ -397,6 +405,7 @@ mod tests {
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::settings;
use crate::settings::types::PythonVersion;
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
@@ -485,9 +494,9 @@ mod tests {
#[test_case(CheckCode::D414, Path::new("sections.py"); "D414")]
#[test_case(CheckCode::D415, Path::new("D.py"); "D415")]
#[test_case(CheckCode::D416, Path::new("D.py"); "D416")]
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
#[test_case(CheckCode::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
#[test_case(CheckCode::D417, Path::new("canonical_google_examples.py"); "D417_2")]
#[test_case(CheckCode::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
#[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::E402, Path::new("E402.py"); "E402")]
@@ -503,6 +512,7 @@ mod tests {
#[test_case(CheckCode::E742, Path::new("E742.py"); "E742")]
#[test_case(CheckCode::E743, Path::new("E743.py"); "E743")]
#[test_case(CheckCode::E999, Path::new("E999.py"); "E999")]
#[test_case(CheckCode::ERA001, Path::new("ERA001.py"); "ERA001")]
#[test_case(CheckCode::F401, Path::new("F401_0.py"); "F401_0")]
#[test_case(CheckCode::F401, Path::new("F401_1.py"); "F401_1")]
#[test_case(CheckCode::F401, Path::new("F401_2.py"); "F401_2")]
@@ -516,6 +526,19 @@ mod tests {
#[test_case(CheckCode::F405, Path::new("F405.py"); "F405")]
#[test_case(CheckCode::F406, Path::new("F406.py"); "F406")]
#[test_case(CheckCode::F407, Path::new("F407.py"); "F407")]
#[test_case(CheckCode::F501, Path::new("F50x.py"); "F501")]
#[test_case(CheckCode::F502, Path::new("F502.py"); "F502_1")]
#[test_case(CheckCode::F502, Path::new("F50x.py"); "F502_0")]
#[test_case(CheckCode::F503, Path::new("F503.py"); "F503_1")]
#[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")]
#[test_case(CheckCode::F504, Path::new("F504.py"); "F504_1")]
#[test_case(CheckCode::F504, Path::new("F50x.py"); "F504_0")]
#[test_case(CheckCode::F505, Path::new("F504.py"); "F505_1")]
#[test_case(CheckCode::F505, Path::new("F50x.py"); "F505_0")]
#[test_case(CheckCode::F506, Path::new("F50x.py"); "F506")]
#[test_case(CheckCode::F507, Path::new("F50x.py"); "F507")]
#[test_case(CheckCode::F508, Path::new("F50x.py"); "F508")]
#[test_case(CheckCode::F509, Path::new("F50x.py"); "F509")]
#[test_case(CheckCode::F521, Path::new("F521.py"); "F521")]
#[test_case(CheckCode::F522, Path::new("F522.py"); "F522")]
#[test_case(CheckCode::F523, Path::new("F523.py"); "F523")]
@@ -546,6 +569,9 @@ mod tests {
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
#[test_case(CheckCode::F841, Path::new("F841.py"); "F841")]
#[test_case(CheckCode::F901, Path::new("F901.py"); "F901")]
#[test_case(CheckCode::FBT001, Path::new("FBT.py"); "FBT001")]
#[test_case(CheckCode::FBT002, Path::new("FBT.py"); "FBT002")]
#[test_case(CheckCode::FBT003, Path::new("FBT.py"); "FBT003")]
#[test_case(CheckCode::N801, Path::new("N801.py"); "N801")]
#[test_case(CheckCode::N802, Path::new("N802.py"); "N802")]
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
@@ -561,12 +587,23 @@ mod tests {
#[test_case(CheckCode::N816, Path::new("N816.py"); "N816")]
#[test_case(CheckCode::N817, Path::new("N817.py"); "N817")]
#[test_case(CheckCode::N818, Path::new("N818.py"); "N818")]
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_0.py"); "RUF101_0")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_1.py"); "RUF101_1")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_2.py"); "RUF101_2")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_3.py"); "RUF101_3")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_4.py"); "RUF101_4")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_5.py"); "RUF101_5")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_6.py"); "RUF101_6")]
#[test_case(CheckCode::S101, Path::new("S101.py"); "S101")]
#[test_case(CheckCode::S102, Path::new("S102.py"); "S102")]
#[test_case(CheckCode::S104, Path::new("S104.py"); "S104")]
#[test_case(CheckCode::S105, Path::new("S105.py"); "S105")]
#[test_case(CheckCode::S106, Path::new("S106.py"); "S106")]
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
#[test_case(CheckCode::T100, Path::new("T100.py"); "T100")]
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]
@@ -593,16 +630,6 @@ mod tests {
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
#[test_case(CheckCode::W605, Path::new("W605_0.py"); "W605_0")]
#[test_case(CheckCode::W605, Path::new("W605_1.py"); "W605_1")]
#[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")]
#[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")]
#[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_0.py"); "RUF101_0")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_1.py"); "RUF101_1")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_2.py"); "RUF101_2")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_3.py"); "RUF101_3")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_4.py"); "RUF101_4")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_5.py"); "RUF101_5")]
#[test_case(CheckCode::RUF101, Path::new("RUF101_6.py"); "RUF101_6")]
#[test_case(CheckCode::YTT101, Path::new("YTT101.py"); "YTT101")]
#[test_case(CheckCode::YTT102, Path::new("YTT102.py"); "YTT102")]
#[test_case(CheckCode::YTT103, Path::new("YTT103.py"); "YTT103")]
@@ -613,9 +640,6 @@ mod tests {
#[test_case(CheckCode::YTT301, Path::new("YTT301.py"); "YTT301")]
#[test_case(CheckCode::YTT302, Path::new("YTT302.py"); "YTT302")]
#[test_case(CheckCode::YTT303, Path::new("YTT303.py"); "YTT303")]
#[test_case(CheckCode::FBT001, Path::new("FBT.py"); "FBT001")]
#[test_case(CheckCode::FBT002, Path::new("FBT.py"); "FBT002")]
#[test_case(CheckCode::FBT003, Path::new("FBT.py"); "FBT003")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
@@ -678,4 +702,64 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_585_p37() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py37,
..settings::Settings::for_rule(CheckCode::U006)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_585_py310() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py310,
..settings::Settings::for_rule(CheckCode::U006)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_604_p37() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py37,
..settings::Settings::for_rule(CheckCode::U007)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations_pep_604_py310() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
target_version: PythonVersion::Py310,
..settings::Settings::for_rule(CheckCode::U007)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -23,14 +23,15 @@ use ::ruff::fs::iter_python_files;
use ::ruff::linter::{add_noqa_to_path, autoformat_path, lint_path, lint_stdin, Diagnostics};
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::printer::Printer;
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
use ::ruff::{cache, commands};
use anyhow::Result;
use clap::Parser;
use clap::{CommandFactory, Parser};
use colored::Colorize;
use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
@@ -189,23 +190,18 @@ fn inner_main() -> Result<ExitCode> {
// Extract command-line arguments.
let cli = Cli::parse();
let fix = cli.fix();
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut std::io::stdout());
return Ok(ExitCode::SUCCESS);
}
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&cli.files);
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
let pyproject = cli
.config
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
// Reconcile configuration from pyproject.toml and command-line arguments.
let mut configuration =
@@ -237,6 +233,9 @@ fn inner_main() -> Result<ExitCode> {
if !cli.unfixable.is_empty() {
configuration.unfixable = cli.unfixable;
}
if let Some(format) = cli.format {
configuration.format = format;
}
if let Some(line_length) = cli.line_length {
configuration.line_length = line_length;
}
@@ -256,11 +255,6 @@ fn inner_main() -> Result<ExitCode> {
configuration.show_source = true;
}
if let Some(code) = cli.explain {
commands::explain(&code, cli.format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings && cli.show_files {
eprintln!("Error: specify --show-settings or show-files (not both).");
return Ok(ExitCode::FAILURE);
@@ -274,6 +268,30 @@ fn inner_main() -> Result<ExitCode> {
let fix_enabled: bool = configuration.fix;
let settings = Settings::from_configuration(configuration, project_root.as_ref())?;
// If we're using JSON, override the log level.
let log_level = if matches!(settings.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
log_level
};
set_up_logging(&log_level)?;
// Now that we've inferred the appropriate log level, add some debug
// information.
match &project_root {
Some(path) => debug!("Found project root at: {:?}", path),
None => debug!("Unable to identify project root; assuming current directory..."),
};
match &pyproject {
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
if let Some(code) = cli.explain {
commands::explain(&code, settings.format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS);
@@ -286,24 +304,21 @@ fn inner_main() -> Result<ExitCode> {
cache_enabled = false;
}
let printer = Printer::new(&cli.format, &log_level);
let printer = Printer::new(&settings.format, &log_level);
if cli.watch {
if settings.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
if fix_enabled {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
if cli.add_noqa {
eprintln!("Warning: --no-qa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
// Perform an initial run instantly.
printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");

View File

@@ -1,16 +1,10 @@
use std::cmp::Ordering;
use std::fmt;
use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use colored::Colorize;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::fs::relativize_path;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -50,57 +44,6 @@ impl PartialOrd for Message {
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&self.filename)).bold(),
":".cyan(),
self.location.row(),
":".cyan(),
self.location.column(),
":".cyan(),
self.kind.code().as_ref().red().bold(),
self.kind.body(),
);
match &self.source {
None => write!(f, "{label}"),
Some(source) => {
let snippet = Snippet {
title: Some(Annotation {
label: Some(&label),
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: self.location.row(),
annotations: vec![SourceAnnotation {
label: self.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// `split_once(' ')` strips "error: " from `message`.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once(' ').unwrap();
write!(f, "{message}")
}
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Source {
pub contents: String,

View File

@@ -3,6 +3,7 @@ use std::fs;
use std::path::Path;
use anyhow::Result;
use itertools::Itertools;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -13,9 +14,9 @@ static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
)
.expect("Invalid regex")
.unwrap()
});
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
#[derive(Debug)]
pub enum Directive<'a> {
@@ -52,12 +53,13 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
}
pub fn add_noqa(
path: &Path,
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
path: &Path,
external: &BTreeSet<String>,
) -> Result<usize> {
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for);
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for, external);
fs::write(path, output)?;
Ok(count)
}
@@ -66,6 +68,7 @@ fn add_noqa_inner(
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &BTreeSet<String>,
) -> (usize, String) {
let lines: Vec<&str> = contents.lines().collect();
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
@@ -112,25 +115,47 @@ fn add_noqa_inner(
output.push('\n');
count += 1;
}
Directive::All(_, start, _) | Directive::Codes(_, start, ..) => {
let mut new_line = String::new();
Directive::All(_, start, _) => {
// Add existing content.
new_line.push_str(&line[..start].trim_end());
output.push_str(line[..start].trim_end());
// Add `noqa` directive.
new_line.push_str(" # noqa: ");
output.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes.iter().map(AsRef::as_ref).collect();
let codes: Vec<&str> =
codes.iter().map(AsRef::as_ref).sorted_unstable().collect();
let suffix = codes.join(", ");
new_line.push_str(&suffix);
output.push_str(&suffix);
output.push('\n');
count += 1;
}
Directive::Codes(_, start, _, existing) => {
// Reconstruct the line based on the preserved check codes.
// This enables us to tally the number of edits.
let mut formatted = String::new();
output.push_str(&new_line);
// Add existing content.
formatted.push_str(line[..start].trim_end());
// Add `noqa` directive.
formatted.push_str(" # noqa: ");
// Add codes.
let codes: Vec<&str> = codes
.iter()
.map(AsRef::as_ref)
.chain(existing.into_iter().filter(|code| external.contains(*code)))
.sorted_unstable()
.collect();
let suffix = codes.join(", ");
formatted.push_str(&suffix);
output.push_str(&formatted);
output.push('\n');
// Only count if the new line is an actual edit.
if &new_line != line {
if &formatted != line {
count += 1;
}
}
@@ -144,6 +169,7 @@ fn add_noqa_inner(
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
@@ -171,7 +197,8 @@ mod tests {
let checks = vec![];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 0);
assert_eq!(output.trim(), contents.trim());
@@ -184,7 +211,8 @@ mod tests {
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
@@ -206,7 +234,8 @@ mod tests {
];
let contents = "x = 1 # noqa: E741";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
@@ -228,7 +257,8 @@ mod tests {
];
let contents = "x = 1 # noqa";
let noqa_line_for = IntMap::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for);
let external = BTreeSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
}

View File

@@ -1,20 +1,22 @@
use std::collections::BTreeMap;
use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use anyhow::Result;
use clap::ValueEnum;
use colored::Colorize;
use itertools::iterate;
use rustpython_parser::ast::Location;
use serde::Serialize;
use crate::checks::{CheckCode, CheckKind};
use crate::fs::relativize_path;
use crate::linter::Diagnostics;
use crate::logging::LogLevel;
use crate::message::Message;
use crate::settings::types::SerializationFormat;
use crate::tell_user;
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Debug)]
pub enum SerializationFormat {
Text,
Json,
}
#[derive(Serialize)]
struct ExpandedMessage<'a> {
kind: &'a CheckKind,
@@ -41,6 +43,28 @@ impl<'a> Printer<'a> {
}
}
fn pre_text(&self, diagnostics: &Diagnostics) {
if self.log_level >= &LogLevel::Default {
if diagnostics.fixed > 0 {
println!(
"Found {} error(s) ({} fixed).",
diagnostics.messages.len(),
diagnostics.fixed,
);
} else if !diagnostics.messages.is_empty() {
println!("Found {} error(s).", diagnostics.messages.len());
}
}
}
fn post_text(&self, num_fixable: usize) {
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.");
}
}
}
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
@@ -72,28 +96,101 @@ impl<'a> Printer<'a> {
)?
);
}
SerializationFormat::Text => {
if self.log_level >= &LogLevel::Default {
if diagnostics.fixed > 0 {
println!(
"Found {} error(s) ({} fixed).",
diagnostics.messages.len(),
diagnostics.fixed,
);
} else if !diagnostics.messages.is_empty() {
println!("Found {} error(s).", diagnostics.messages.len());
}
SerializationFormat::Junit => {
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
let mut report = Report::new("ruff");
for (filename, messages) in grouped_messages {
let mut test_suite = TestSuite::new(filename);
test_suite
.extra
.insert("package".to_string(), "org.ruff".to_string());
for message in messages {
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
status.set_message(message.kind.body());
status.set_description(format!(
"line {}, col {}, {}",
message.location.row(),
message.location.column(),
message.kind.body()
));
let mut case =
TestCase::new(format!("org.ruff.{}", message.kind.code()), status);
let file_path = Path::new(filename);
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let classname = file_path.parent().unwrap().join(file_stem);
case.set_classname(classname.to_str().unwrap());
case.extra
.insert("line".to_string(), message.location.row().to_string());
case.extra
.insert("column".to_string(), message.location.column().to_string());
test_suite.add_test_case(case);
}
report.add_test_suite(test_suite);
}
println!("{}", report.to_string().unwrap());
}
SerializationFormat::Text => {
self.pre_text(diagnostics);
for message in &diagnostics.messages {
println!("{message}");
print_message(message);
}
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.");
}
self.post_text(num_fixable);
}
SerializationFormat::Grouped => {
self.pre_text(diagnostics);
println!();
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
for (filename, messages) in grouped_messages {
// Compute the maximum number of digits in the row and column, for messages in
// this file.
let row_length = num_digits(
messages
.iter()
.map(|message| message.location.row())
.max()
.unwrap(),
);
let column_length = num_digits(
messages
.iter()
.map(|message| message.location.column())
.max()
.unwrap(),
);
// Print the filename.
println!("{}:", relativize_path(Path::new(&filename)).underline());
// Print each message.
for message in messages {
print_grouped_message(message, row_length, column_length);
}
println!();
}
self.post_text(num_fixable);
}
}
@@ -117,7 +214,7 @@ impl<'a> Printer<'a> {
println!();
}
for message in &diagnostics.messages {
println!("{message}");
print_message(message);
}
}
@@ -130,3 +227,107 @@ impl<'a> Printer<'a> {
Ok(())
}
}
fn num_digits(n: usize) -> usize {
iterate(n, |&n| n / 10)
.take_while(|&n| n > 0)
.count()
.max(1)
}
/// Print a single `Message` with full details.
fn print_message(message: &Message) {
let label = format!(
"{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&message.filename)).bold(),
":".cyan(),
message.location.row(),
":".cyan(),
message.location.column(),
":".cyan(),
message.kind.code().as_ref().red().bold(),
message.kind.body(),
);
println!("{label}");
if let Some(source) = &message.source {
let snippet = Snippet {
title: Some(Annotation {
label: None,
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// Skip the first line, since we format the `label` ourselves.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap();
println!("{message}");
}
}
/// Print a grouped `Message`, assumed to be printed in a group with others from
/// the same file.
fn print_grouped_message(message: &Message, row_length: usize, column_length: usize) {
let label = format!(
" {}{}{}{}{} {} {}",
" ".repeat(row_length - num_digits(message.location.row())),
message.location.row(),
":".cyan(),
message.location.column(),
" ".repeat(column_length - num_digits(message.location.column())),
message.kind.code().as_ref().red().bold(),
message.kind.body(),
);
println!("{label}");
if let Some(source) = &message.source {
let snippet = Snippet {
title: Some(Annotation {
label: None,
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: message.location.row(),
annotations: vec![SourceAnnotation {
label: message.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..FormatOptions::default()
},
};
// Skip the first line, since we format the `label` ourselves.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once('\n').unwrap();
let message = textwrap::indent(message, " ");
println!("{message}");
}
}

View File

@@ -4,7 +4,7 @@ use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::FxHashSet;
use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind};
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
use crate::ast::types::Range;
use crate::ast::whitespace;
@@ -15,7 +15,7 @@ use crate::docstrings::constants;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::sections::{section_contexts, SectionContext};
use crate::docstrings::styles::SectionStyle;
use crate::visibility::{is_init, is_magic, is_overload, is_staticmethod, Visibility};
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
/// D100, D101, D102, D103, D104, D105, D106, D107
pub fn not_missing(
@@ -88,7 +88,7 @@ pub fn not_missing(
}
}
DefinitionKind::Method(stmt) => {
if is_overload(stmt) {
if is_overload(stmt) || is_override(stmt) {
true
} else if is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::D105) {
@@ -1303,8 +1303,9 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
args: arguments, ..
} = &parent.node
{
// Collect all the arguments into a single vector.
let mut all_arguments: Vec<&Arg> = arguments
// Look for arguments that weren't included in the docstring.
let mut missing_arg_names: BTreeSet<String> = BTreeSet::default();
for arg in arguments
.args
.iter()
.chain(arguments.posonlyargs.iter())
@@ -1316,33 +1317,38 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
&& !is_staticmethod(parent),
),
)
.collect();
{
let arg_name = arg.node.arg.as_str();
if !arg_name.starts_with('_') && !docstrings_args.contains(&arg_name) {
missing_arg_names.insert(arg_name.to_string());
}
}
// Check specifically for `vararg` and `kwarg`, which can be prefixed with a
// single or double star, respectively.
if let Some(arg) = &arguments.vararg {
all_arguments.push(arg);
let arg_name = arg.node.arg.as_str();
let starred_arg_name = format!("*{arg_name}");
if !arg_name.starts_with('_')
&& !docstrings_args.contains(&arg_name)
&& !docstrings_args.contains(&starred_arg_name.as_str())
{
missing_arg_names.insert(starred_arg_name);
}
}
if let Some(arg) = &arguments.kwarg {
all_arguments.push(arg);
}
// Look for arguments that weren't included in the docstring.
let mut missing_args: BTreeSet<&str> = BTreeSet::default();
for arg in all_arguments {
let arg_name = arg.node.arg.as_str();
if arg_name.starts_with('_') {
continue;
let starred_arg_name = format!("**{arg_name}");
if !arg_name.starts_with('_')
&& !docstrings_args.contains(&arg_name)
&& !docstrings_args.contains(&starred_arg_name.as_str())
{
missing_arg_names.insert(starred_arg_name);
}
if docstrings_args.contains(&arg_name) {
continue;
}
missing_args.insert(arg_name);
}
if !missing_args.is_empty() {
let names = missing_args
.into_iter()
.map(String::from)
.sorted()
.collect();
if !missing_arg_names.is_empty() {
let names = missing_arg_names.into_iter().sorted().collect();
checker.add_check(Check::new(
CheckKind::DocumentAllArguments(names),
Range::from_located(parent),
@@ -1354,7 +1360,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
static GOOGLE_ARGS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").expect("Invalid regex"));
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").unwrap());
fn args_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
let mut args_sections: Vec<String> = vec![];

102
src/pyflakes/cformat.rs Normal file
View File

@@ -0,0 +1,102 @@
//! Implements helper functions for using vendored/cformat.rs
use std::convert::TryFrom;
use std::str::FromStr;
use rustc_hash::FxHashSet;
use crate::vendored::cformat::{
CFormatError, CFormatPart, CFormatQuantity, CFormatSpec, CFormatString,
};
pub(crate) struct CFormatSummary {
pub starred: bool,
pub num_positional: usize,
pub keywords: FxHashSet<String>,
}
impl TryFrom<&str> for CFormatSummary {
type Error = CFormatError;
fn try_from(literal: &str) -> Result<Self, Self::Error> {
let format_string = CFormatString::from_str(literal)?;
let mut starred = false;
let mut num_positional = 0;
let mut keywords = FxHashSet::default();
for format_part in format_string.parts {
if let CFormatPart::Spec(CFormatSpec {
mapping_key,
min_field_width,
precision,
..
}) = format_part.1
{
match mapping_key {
Some(k) => {
keywords.insert(k);
}
None => {
num_positional += 1;
}
};
if min_field_width == Some(CFormatQuantity::FromValuesTuple) {
num_positional += 1;
starred = true;
}
if precision == Some(CFormatQuantity::FromValuesTuple) {
num_positional += 1;
starred = true;
}
}
}
Ok(CFormatSummary {
starred,
num_positional,
keywords,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cformat_summary() {
let literal = "%(foo)s %s %d %(bar)x";
let expected_positional = 2;
let expected_keywords = ["foo", "bar"].into_iter().map(String::from).collect();
let format_summary = CFormatSummary::try_from(literal).unwrap();
assert!(!format_summary.starred);
assert_eq!(format_summary.num_positional, expected_positional);
assert_eq!(format_summary.keywords, expected_keywords);
}
#[test]
fn test_cformat_summary_starred() {
let format_summary1 = CFormatSummary::try_from("%*s %*d").unwrap();
assert!(format_summary1.starred);
assert_eq!(format_summary1.num_positional, 4);
let format_summary2 = CFormatSummary::try_from("%s %.*d").unwrap();
assert!(format_summary2.starred);
assert_eq!(format_summary2.num_positional, 3);
let format_summary3 = CFormatSummary::try_from("%s %*.*d").unwrap();
assert!(format_summary3.starred);
assert_eq!(format_summary3.num_positional, 4);
let format_summary4 = CFormatSummary::try_from("%s %1d").unwrap();
assert!(!format_summary4.starred);
}
#[test]
fn test_cformat_summary_invalid() {
assert!(CFormatSummary::try_from("%").is_err());
assert!(CFormatSummary::try_from("%(foo).").is_err());
}
}

View File

@@ -9,6 +9,7 @@ use rustpython_parser::ast::{
use crate::ast::types::{BindingKind, FunctionScope, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pyflakes::cformat::CFormatSummary;
use crate::pyflakes::format::FormatSummary;
fn has_star_star_kwargs(keywords: &[Keyword]) -> bool {
@@ -23,6 +24,216 @@ fn has_star_args(args: &[Expr]) -> bool {
.any(|a| matches!(&a.node, ExprKind::Starred { .. }))
}
/// F502
pub(crate) fn percent_format_expected_mapping(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if summary.keywords.is_empty() {
None
} else {
// Tuple, List, Set (+comprehensions)
match right.node {
ExprKind::List { .. }
| ExprKind::Tuple { .. }
| ExprKind::Set { .. }
| ExprKind::ListComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::GeneratorExp { .. } => Some(Check::new(
CheckKind::PercentFormatExpectedMapping,
location,
)),
_ => None,
}
}
}
/// F503
pub(crate) fn percent_format_expected_sequence(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if summary.num_positional <= 1 {
None
} else {
match right.node {
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
CheckKind::PercentFormatExpectedSequence,
location,
)),
_ => None,
}
}
}
/// F504
pub(crate) fn percent_format_extra_named_arguments(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if summary.num_positional > 0 {
return None;
}
if let ExprKind::Dict { keys, values } = &right.node {
if values.len() > keys.len() {
return None; // contains **x splat
}
let missing: Vec<&String> = keys
.iter()
.filter_map(|k| match &k.node {
// We can only check that string literals exist
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
if summary.keywords.contains(value) {
None
} else {
Some(value)
}
}
_ => None,
})
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::PercentFormatExtraNamedArguments(
missing.iter().map(|&s| s.clone()).collect(),
),
location,
))
}
} else {
None
}
}
/// F505
pub(crate) fn percent_format_missing_arguments(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if summary.num_positional > 0 {
return None;
}
if let ExprKind::Dict { keys, values } = &right.node {
if values.len() > keys.len() {
return None; // contains **x splat
}
let mut keywords = FxHashSet::default();
for key in keys {
match &key.node {
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
keywords.insert(value);
}
_ => {
return None; // Dynamic keys present
}
}
}
let missing: Vec<&String> = summary
.keywords
.iter()
.filter(|k| !keywords.contains(k))
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::PercentFormatMissingArgument(
missing.iter().map(|&s| s.clone()).collect(),
),
location,
))
}
} else {
None
}
}
/// F506
pub(crate) fn percent_format_mixed_positional_and_named(
summary: &CFormatSummary,
location: Range,
) -> Option<Check> {
if summary.num_positional == 0 || summary.keywords.is_empty() {
None
} else {
Some(Check::new(
CheckKind::PercentFormatMixedPositionalAndNamed,
location,
))
}
}
/// F507
pub(crate) fn percent_format_positional_count_mismatch(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if !summary.keywords.is_empty() {
return None;
}
match &right.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } | ExprKind::Set { elts, .. } => {
let mut found = 0;
for elt in elts {
if let ExprKind::Starred { .. } = &elt.node {
return None;
}
found += 1;
}
if found == summary.num_positional {
None
} else {
Some(Check::new(
CheckKind::PercentFormatPositionalCountMismatch(summary.num_positional, found),
location,
))
}
}
_ => None,
}
}
/// F508
pub(crate) fn percent_format_star_requires_sequence(
summary: &CFormatSummary,
right: &Expr,
location: Range,
) -> Option<Check> {
if summary.starred {
match &right.node {
ExprKind::Dict { .. } | ExprKind::DictComp { .. } => Some(Check::new(
CheckKind::PercentFormatStarRequiresSequence,
location,
)),
_ => None,
}
} else {
None
}
}
/// F522
pub(crate) fn string_dot_format_extra_named_arguments(
summary: &FormatSummary,
@@ -38,16 +249,17 @@ pub(crate) fn string_dot_format_extra_named_arguments(
arg.as_ref()
});
let missing: Vec<String> = keywords
let missing: Vec<&String> = keywords
.filter(|&k| !summary.keywords.contains(k))
.cloned()
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::StringDotFormatExtraNamedArguments(missing),
CheckKind::StringDotFormatExtraNamedArguments(
missing.iter().map(|&s| s.clone()).collect(),
),
location,
))
}

View File

@@ -1,7 +1,7 @@
use anyhow::Result;
use libcst_native::{
Codegen, CodegenState, CompOp, Comparison, ComparisonTarget, Expr, Expression, ImportNames,
NameOrAttribute, SmallStatement, Statement,
SmallStatement, Statement,
};
use rustpython_ast::Stmt;
@@ -14,7 +14,7 @@ use crate::source_code_locator::SourceCodeLocator;
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &SourceCodeLocator,
full_names: &[&str],
unused_imports: &Vec<(&String, &Range)>,
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
@@ -22,106 +22,43 @@ pub fn remove_unused_imports(
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
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"
));
};
let aliases = &mut body.names;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if full_names.contains(&compose_module_path(&alias.name).as_str()) {
removable.push(index);
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
helpers::remove_stmt(stmt, parent, deleted)
} else {
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
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"
));
};
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let NameOrAttribute::N(name) = &alias.name {
let import_name = name.value.to_string();
let full_name = body
.module
.as_ref()
.map(compose_module_path)
.map(|module_name| format!("{module_name}.{import_name}"))
.unwrap_or(import_name);
if full_names.contains(&full_name.as_str()) {
removable.push(index);
let (aliases, import_module) = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => Ok((&mut import_body.names, None)),
Some(SmallStatement::ImportFrom(import_body)) => {
if let ImportNames::Aliases(names) = &mut import_body.names {
Ok((names, import_body.module.as_ref()))
} else {
Err(anyhow::anyhow!("Expected node to be: Aliases"))
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
_ => Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom or SmallStatement::Import"
)),
}?;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
for (name_to_remove, _) in unused_imports {
let alias_index = aliases.iter().position(|alias| {
let full_name = match import_module {
Some(module_name) => format!(
"{}.{}",
compose_module_path(module_name),
compose_module_path(&alias.name)
),
None => compose_module_path(&alias.name),
};
&full_name.as_str() == name_to_remove
});
if let Some(index) = alias_index {
aliases.remove(index);
}
}
if let Some(alias) = aliases.last_mut() {

View File

@@ -1,3 +1,4 @@
pub mod cformat;
pub mod checks;
pub mod fixes;
pub mod format;

View File

@@ -111,10 +111,7 @@ pub fn remove_super_arguments(locator: &SourceCodeLocator, expr: &Expr) -> Optio
let range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);
let mut tree = match libcst_native::parse_module(&contents, None) {
Ok(m) => m,
Err(_) => return None,
};
let mut tree = libcst_native::parse_module(&contents, None).ok()?;
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
@@ -150,22 +147,16 @@ pub fn remove_unnecessary_future_import(
let module_text = locator.slice_source_code_range(&Range::from_located(stmt));
let mut tree = match_module(&module_text)?;
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple"));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom"
));
};
let aliases = if let ImportNames::Aliases(aliases) = &mut body.names {
aliases
} else {
let ImportNames::Aliases(aliases) = &mut body.names else {
return Err(anyhow::anyhow!("Expected node to be: Aliases"));
};

View File

@@ -1,4 +1,5 @@
mod checks;
pub mod fixes;
pub mod plugins;
pub mod settings;
pub mod types;

View File

@@ -64,7 +64,6 @@ fn match_open(expr: &Expr) -> (Option<&Expr>, Vec<Keyword>) {
keywords,
} = &expr.node
{
// TODO(andberger): Verify that "open" is still bound to the built-in function.
if match_name_or_attr(func, OPEN_FUNC_NAME) {
// Return the "open mode" parameter and keywords.
return (args.get(1), keywords.clone());
@@ -149,6 +148,10 @@ fn create_remove_param_fix(
/// U015
pub fn redundant_open_modes(checker: &mut Checker, expr: &Expr) {
// If `open` has been rebound, skip this check entirely.
if !checker.is_builtin(OPEN_FUNC_NAME) {
return;
}
let (mode_param, keywords): (Option<&Expr>, Vec<Keyword>) = match_open(expr);
if mode_param.is_none() && !keywords.is_empty() {
if let Some(value) = keywords.iter().find_map(|keyword| {

22
src/pyupgrade/settings.rs Normal file
View File

@@ -0,0 +1,22 @@
//! Settings for the `pyupgrade` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub keep_runtime_typing: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub keep_runtime_typing: bool,
}
impl Settings {
pub fn from_options(options: &Options) -> Self {
Self {
keep_runtime_typing: options.keep_runtime_typing.unwrap_or_default(),
}
}
}

View File

@@ -11,10 +11,10 @@ use regex::Regex;
use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
#[derive(Debug)]
@@ -24,8 +24,10 @@ pub struct Configuration {
pub extend_exclude: Vec<FilePattern>,
pub extend_ignore: Vec<CheckCodePrefix>,
pub extend_select: Vec<CheckCodePrefix>,
pub external: Vec<String>,
pub fix: bool,
pub fixable: Vec<CheckCodePrefix>,
pub format: SerializationFormat,
pub ignore: Vec<CheckCodePrefix>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
@@ -42,6 +44,7 @@ pub struct Configuration {
pub isort: isort::settings::Settings,
pub mccabe: mccabe::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
pub pyupgrade: pyupgrade::settings::Settings,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -116,9 +119,11 @@ impl Configuration {
.select
.unwrap_or_else(|| vec![CheckCodePrefix::E, CheckCodePrefix::F]),
extend_select: options.extend_select.unwrap_or_default(),
external: options.external.unwrap_or_default(),
fix: options.fix.unwrap_or_default(),
fixable: options.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
unfixable: options.unfixable.unwrap_or_default(),
format: options.format.unwrap_or(SerializationFormat::Text),
ignore: options.ignore.unwrap_or_default(),
line_length: options.line_length.unwrap_or(88),
per_file_ignores: options
@@ -160,6 +165,11 @@ impl Configuration {
.pep8_naming
.map(pep8_naming::settings::Settings::from_options)
.unwrap_or_default(),
pyupgrade: options
.pyupgrade
.as_ref()
.map(pyupgrade::settings::Settings::from_options)
.unwrap_or_default(),
})
}
}

View File

@@ -15,10 +15,10 @@ use rustc_hash::FxHashSet;
use crate::checks::CheckCode;
use crate::checks_gen::{CheckCodePrefix, PrefixSpecificity};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion};
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
pub mod configuration;
@@ -32,7 +32,9 @@ pub struct Settings {
pub enabled: FxHashSet<CheckCode>,
pub exclude: GlobSet,
pub extend_exclude: GlobSet,
pub external: BTreeSet<String>,
pub fixable: FxHashSet<CheckCode>,
pub format: SerializationFormat,
pub line_length: usize,
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)>,
pub show_source: bool,
@@ -46,6 +48,7 @@ pub struct Settings {
pub isort: isort::settings::Settings,
pub mccabe: mccabe::settings::Settings,
pub pep8_naming: pep8_naming::settings::Settings,
pub pyupgrade: pyupgrade::settings::Settings,
}
impl Settings {
@@ -69,7 +72,9 @@ impl Settings {
),
exclude: resolve_globset(config.exclude, project_root)?,
extend_exclude: resolve_globset(config.extend_exclude, project_root)?,
external: BTreeSet::from_iter(config.external),
fixable: resolve_codes(&config.fixable, &config.unfixable),
format: config.format,
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_quotes: config.flake8_quotes,
@@ -78,6 +83,7 @@ impl Settings {
mccabe: config.mccabe,
line_length: config.line_length,
pep8_naming: config.pep8_naming,
pyupgrade: config.pyupgrade,
per_file_ignores: resolve_per_file_ignores(config.per_file_ignores, project_root)?,
src: config.src,
target_version: config.target_version,
@@ -89,11 +95,14 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter([check_code.clone()]),
fixable: FxHashSet::from_iter([check_code]),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: BTreeSet::default(),
fixable: FxHashSet::from_iter([check_code]),
format: SerializationFormat::Text,
line_length: 88,
per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
@@ -103,7 +112,7 @@ impl Settings {
isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
pyupgrade: pyupgrade::settings::Settings::default(),
}
}
@@ -111,11 +120,14 @@ impl Settings {
Self {
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter(check_codes.clone()),
fixable: FxHashSet::from_iter(check_codes),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: BTreeSet::default(),
fixable: FxHashSet::from_iter(check_codes),
format: SerializationFormat::Text,
line_length: 88,
per_file_ignores: vec![],
show_source: false,
src: vec![path_dedot::CWD.clone()],
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
@@ -125,7 +137,7 @@ impl Settings {
isort: isort::settings::Settings::default(),
mccabe: mccabe::settings::Settings::default(),
pep8_naming: pep8_naming::settings::Settings::default(),
show_source: false,
pyupgrade: pyupgrade::settings::Settings::default(),
}
}
}
@@ -137,6 +149,7 @@ impl Hash for Settings {
for value in &self.enabled {
value.hash(state);
}
self.external.hash(state);
for value in &self.fixable {
value.hash(state);
}
@@ -156,6 +169,7 @@ impl Hash for Settings {
self.isort.hash(state);
self.mccabe.hash(state);
self.pep8_naming.hash(state);
self.pyupgrade.hash(state);
}
}

View File

@@ -4,10 +4,10 @@ use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::PythonVersion;
use crate::settings::types::{PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
pep8_naming,
pep8_naming, pyupgrade,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
@@ -18,8 +18,10 @@ pub struct Options {
pub extend_exclude: Option<Vec<String>>,
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
pub extend_select: Option<Vec<CheckCodePrefix>>,
pub external: Option<Vec<String>>,
pub fix: Option<bool>,
pub fixable: Option<Vec<CheckCodePrefix>>,
pub format: Option<SerializationFormat>,
pub ignore: Option<Vec<CheckCodePrefix>>,
pub line_length: Option<usize>,
pub select: Option<Vec<CheckCodePrefix>>,
@@ -35,6 +37,7 @@ pub struct Options {
pub isort: Option<isort::settings::Options>,
pub mccabe: Option<mccabe::settings::Options>,
pub pep8_naming: Option<pep8_naming::settings::Options>,
pub pyupgrade: Option<pyupgrade::settings::Options>,
// Tables are required to go last.
pub per_file_ignores: Option<FxHashMap<String, Vec<CheckCodePrefix>>>,
}

View File

@@ -138,6 +138,7 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -147,6 +148,7 @@ mod tests {
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -155,6 +157,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -175,6 +178,7 @@ line-length = 79
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -184,6 +188,7 @@ line-length = 79
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -192,6 +197,7 @@ line-length = 79
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -213,9 +219,11 @@ exclude = ["foo.py"]
extend_exclude: None,
select: None,
extend_select: None,
external: None,
ignore: None,
extend_ignore: None,
fixable: None,
format: None,
unfixable: None,
per_file_ignores: None,
dummy_variable_rgx: None,
@@ -229,6 +237,7 @@ exclude = ["foo.py"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -249,6 +258,7 @@ select = ["E501"]
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
ignore: None,
@@ -258,6 +268,7 @@ select = ["E501"]
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -266,6 +277,7 @@ select = ["E501"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -287,6 +299,7 @@ ignore = ["E501"]
extend_exclude: None,
extend_ignore: None,
extend_select: Some(vec![CheckCodePrefix::M001]),
external: None,
fix: None,
fixable: None,
ignore: Some(vec![CheckCodePrefix::E501]),
@@ -296,6 +309,7 @@ ignore = ["E501"]
show_source: None,
src: None,
target_version: None,
format: None,
unfixable: None,
flake8_annotations: None,
flake8_bugbear: None,
@@ -304,6 +318,7 @@ ignore = ["E501"]
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
})
})
);
@@ -369,9 +384,11 @@ other-attribute = 1
]),
select: None,
extend_select: None,
external: Some(vec!["V101".to_string()]),
ignore: None,
extend_ignore: None,
fixable: None,
format: None,
unfixable: None,
per_file_ignores: Some(FxHashMap::from_iter([(
"__init__.py".to_string(),
@@ -422,6 +439,7 @@ other-attribute = 1
]),
staticmethod_decorators: Some(vec!["staticmethod".to_string()]),
}),
pyupgrade: None,
}
);

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{anyhow, Result};
use clap::ValueEnum;
use globset::{Glob, GlobSetBuilder};
use serde::{de, Deserialize, Deserializer, Serialize};
@@ -141,3 +142,12 @@ impl FromStr for PatternPrefixPair {
Ok(Self { pattern, prefix })
}
}
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum SerializationFormat {
Text,
Json,
Junit,
Grouped,
}

View File

@@ -4,10 +4,10 @@ expression: checks
---
- kind: PublicClass
location:
row: 14
row: 15
column: 0
end_location:
row: 67
row: 72
column: 0
fix: ~

View File

@@ -4,26 +4,26 @@ expression: checks
---
- kind: PublicMethod
location:
row: 22
row: 23
column: 4
end_location:
row: 25
row: 26
column: 4
fix: ~
- kind: PublicMethod
location:
row: 51
row: 56
column: 4
end_location:
row: 54
row: 59
column: 4
fix: ~
- kind: PublicMethod
location:
row: 63
row: 68
column: 4
end_location:
row: 67
row: 72
column: 0
fix: ~

View File

@@ -4,10 +4,10 @@ expression: checks
---
- kind: PublicFunction
location:
row: 395
row: 400
column: 0
end_location:
row: 396
row: 401
column: 0
fix: ~

View File

@@ -4,10 +4,10 @@ expression: checks
---
- kind: MagicMethod
location:
row: 59
row: 64
column: 4
end_location:
row: 62
row: 67
column: 4
fix: ~

View File

@@ -4,18 +4,18 @@ expression: checks
---
- kind: PublicInit
location:
row: 55
row: 60
column: 4
end_location:
row: 58
row: 63
column: 4
fix: ~
- kind: PublicInit
location:
row: 529
row: 534
column: 4
end_location:
row: 533
row: 538
column: 0
fix: ~

View File

@@ -5,69 +5,69 @@ expression: checks
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 132
row: 137
column: 4
end_location:
row: 132
row: 137
column: 24
fix:
patch:
content: ""
location:
row: 131
row: 136
column: 0
end_location:
row: 132
row: 137
column: 0
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 146
row: 151
column: 4
end_location:
row: 146
row: 151
column: 37
fix:
patch:
content: ""
location:
row: 145
row: 150
column: 0
end_location:
row: 146
row: 151
column: 0
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 541
row: 546
column: 4
end_location:
row: 544
row: 549
column: 7
fix:
patch:
content: ""
location:
row: 540
row: 545
column: 0
end_location:
row: 541
row: 546
column: 0
- kind:
NoBlankLineBeforeFunction: 1
location:
row: 563
row: 568
column: 4
end_location:
row: 566
row: 571
column: 7
fix:
patch:
content: ""
location:
row: 562
row: 567
column: 0
end_location:
row: 563
row: 568
column: 0

View File

@@ -5,69 +5,69 @@ expression: checks
- kind:
NoBlankLineAfterFunction: 1
location:
row: 137
row: 142
column: 4
end_location:
row: 137
row: 142
column: 24
fix:
patch:
content: ""
location:
row: 138
row: 143
column: 0
end_location:
row: 139
row: 144
column: 0
- kind:
NoBlankLineAfterFunction: 1
location:
row: 146
row: 151
column: 4
end_location:
row: 146
row: 151
column: 37
fix:
patch:
content: ""
location:
row: 147
row: 152
column: 0
end_location:
row: 148
row: 153
column: 0
- kind:
NoBlankLineAfterFunction: 1
location:
row: 550
row: 555
column: 4
end_location:
row: 553
row: 558
column: 7
fix:
patch:
content: ""
location:
row: 554
row: 559
column: 0
end_location:
row: 555
row: 560
column: 0
- kind:
NoBlankLineAfterFunction: 1
location:
row: 563
row: 568
column: 4
end_location:
row: 566
row: 571
column: 7
fix:
patch:
content: ""
location:
row: 567
row: 572
column: 0
end_location:
row: 568
row: 573
column: 0

View File

@@ -5,52 +5,52 @@ expression: checks
- kind:
OneBlankLineBeforeClass: 0
location:
row: 156
row: 161
column: 4
end_location:
row: 156
row: 161
column: 32
fix:
patch:
content: "\n"
location:
row: 156
row: 161
column: 0
end_location:
row: 156
row: 161
column: 0
- kind:
OneBlankLineBeforeClass: 0
location:
row: 187
row: 192
column: 4
end_location:
row: 187
row: 192
column: 45
fix:
patch:
content: "\n"
location:
row: 187
row: 192
column: 0
end_location:
row: 187
row: 192
column: 0
- kind:
OneBlankLineBeforeClass: 0
location:
row: 521
row: 526
column: 4
end_location:
row: 527
row: 532
column: 7
fix:
patch:
content: "\n"
location:
row: 521
row: 526
column: 0
end_location:
row: 521
row: 526
column: 0

View File

@@ -5,35 +5,35 @@ expression: checks
- kind:
OneBlankLineAfterClass: 0
location:
row: 176
row: 181
column: 4
end_location:
row: 176
row: 181
column: 24
fix:
patch:
content: "\n"
location:
row: 177
row: 182
column: 0
end_location:
row: 177
row: 182
column: 0
- kind:
OneBlankLineAfterClass: 0
location:
row: 187
row: 192
column: 4
end_location:
row: 187
row: 192
column: 45
fix:
patch:
content: "\n"
location:
row: 188
row: 193
column: 0
end_location:
row: 188
row: 193
column: 0

View File

@@ -4,34 +4,34 @@ expression: checks
---
- kind: BlankLineAfterSummary
location:
row: 195
row: 200
column: 4
end_location:
row: 198
row: 203
column: 7
fix:
patch:
content: "\n"
location:
row: 196
row: 201
column: 0
end_location:
row: 196
row: 201
column: 0
- kind: BlankLineAfterSummary
location:
row: 205
row: 210
column: 4
end_location:
row: 210
row: 215
column: 7
fix:
patch:
content: "\n"
location:
row: 206
row: 211
column: 0
end_location:
row: 208
row: 213
column: 0

View File

@@ -4,34 +4,34 @@ expression: checks
---
- kind: NoUnderIndentation
location:
row: 227
row: 232
column: 0
end_location:
row: 227
row: 232
column: 0
fix:
patch:
content: " "
location:
row: 227
row: 232
column: 0
end_location:
row: 227
row: 232
column: 0
- kind: NoUnderIndentation
location:
row: 435
row: 440
column: 0
end_location:
row: 435
row: 440
column: 0
fix:
patch:
content: " "
location:
row: 435
row: 440
column: 0
end_location:
row: 435
row: 440
column: 4

View File

@@ -4,50 +4,50 @@ expression: checks
---
- kind: NoOverIndentation
location:
row: 247
row: 252
column: 0
end_location:
row: 247
row: 252
column: 0
fix:
patch:
content: " "
location:
row: 247
row: 252
column: 0
end_location:
row: 247
row: 252
column: 7
- kind: NoOverIndentation
location:
row: 259
row: 264
column: 0
end_location:
row: 259
row: 264
column: 0
fix:
patch:
content: " "
location:
row: 259
row: 264
column: 0
end_location:
row: 259
row: 264
column: 8
- kind: NoOverIndentation
location:
row: 267
row: 272
column: 0
end_location:
row: 267
row: 272
column: 0
fix:
patch:
content: " "
location:
row: 267
row: 272
column: 0
end_location:
row: 267
row: 272
column: 8

View File

@@ -4,18 +4,18 @@ expression: checks
---
- kind: NewLineAfterLastParagraph
location:
row: 276
row: 281
column: 4
end_location:
row: 278
row: 283
column: 19
fix:
patch:
content: "\n "
location:
row: 278
row: 283
column: 16
end_location:
row: 278
row: 283
column: 16

View File

@@ -4,50 +4,50 @@ expression: checks
---
- kind: NoSurroundingWhitespace
location:
row: 283
row: 288
column: 4
end_location:
row: 283
row: 288
column: 33
fix:
patch:
content: Whitespace at the end.
location:
row: 283
row: 288
column: 7
end_location:
row: 283
row: 288
column: 30
- kind: NoSurroundingWhitespace
location:
row: 288
row: 293
column: 4
end_location:
row: 288
row: 293
column: 37
fix:
patch:
content: Whitespace at everywhere.
location:
row: 288
row: 293
column: 7
end_location:
row: 288
row: 293
column: 34
- kind: NoSurroundingWhitespace
location:
row: 294
row: 299
column: 4
end_location:
row: 297
row: 302
column: 7
fix:
patch:
content: Whitespace at the beginning.
location:
row: 294
row: 299
column: 7
end_location:
row: 294
row: 299
column: 36

View File

@@ -5,35 +5,35 @@ expression: checks
- kind:
NoBlankLineBeforeClass: 1
location:
row: 165
row: 170
column: 4
end_location:
row: 165
row: 170
column: 29
fix:
patch:
content: ""
location:
row: 164
row: 169
column: 0
end_location:
row: 165
row: 170
column: 0
- kind:
NoBlankLineBeforeClass: 1
location:
row: 176
row: 181
column: 4
end_location:
row: 176
row: 181
column: 24
fix:
patch:
content: ""
location:
row: 175
row: 180
column: 0
end_location:
row: 176
row: 181
column: 0

View File

@@ -4,10 +4,10 @@ expression: checks
---
- kind: MultiLineSummaryFirstLine
location:
row: 124
row: 129
column: 4
end_location:
row: 126
row: 131
column: 7
fix: ~

View File

@@ -4,154 +4,154 @@ expression: checks
---
- kind: MultiLineSummarySecondLine
location:
row: 195
row: 200
column: 4
end_location:
row: 198
row: 203
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 205
column: 4
end_location:
row: 210
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
column: 4
end_location:
row: 215
column: 4
end_location:
row: 219
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 225
row: 220
column: 4
end_location:
row: 229
row: 224
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 235
row: 230
column: 4
end_location:
row: 239
row: 234
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 240
column: 4
end_location:
row: 244
column: 3
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 245
row: 250
column: 4
end_location:
row: 249
row: 254
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 255
row: 260
column: 4
end_location:
row: 259
row: 264
column: 11
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 265
row: 270
column: 4
end_location:
row: 269
row: 274
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 276
row: 281
column: 4
end_location:
row: 278
row: 283
column: 19
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 294
row: 299
column: 4
end_location:
row: 297
row: 302
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 338
column: 4
end_location:
row: 343
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 378
column: 4
end_location:
row: 381
row: 348
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 387
row: 383
column: 4
end_location:
row: 391
row: 386
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 433
row: 392
column: 4
end_location:
row: 396
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 438
column: 36
end_location:
row: 436
row: 441
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 445
row: 450
column: 4
end_location:
row: 449
row: 454
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 521
row: 526
column: 4
end_location:
row: 527
row: 532
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 541
row: 546
column: 4
end_location:
row: 544
row: 549
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 550
row: 555
column: 4
end_location:
row: 553
row: 558
column: 7
fix: ~
- kind: MultiLineSummarySecondLine
location:
row: 563
row: 568
column: 4
end_location:
row: 566
row: 571
column: 7
fix: ~

View File

@@ -2,14 +2,6 @@
source: src/linter.rs
expression: checks
---
- kind: UsesTripleQuotes
location:
row: 302
column: 4
end_location:
row: 302
column: 19
fix: ~
- kind: UsesTripleQuotes
location:
row: 307
@@ -24,7 +16,7 @@ expression: checks
column: 4
end_location:
row: 312
column: 15
column: 19
fix: ~
- kind: UsesTripleQuotes
location:
@@ -36,10 +28,18 @@ expression: checks
fix: ~
- kind: UsesTripleQuotes
location:
row: 323
row: 322
column: 4
end_location:
row: 323
row: 322
column: 15
fix: ~
- kind: UsesTripleQuotes
location:
row: 328
column: 4
end_location:
row: 328
column: 16
fix: ~

View File

@@ -4,60 +4,52 @@ expression: checks
---
- kind: EndsInPeriod
location:
row: 350
row: 355
column: 4
end_location:
row: 350
row: 355
column: 17
fix: ~
- kind: EndsInPeriod
location:
row: 401
row: 406
column: 24
end_location:
row: 401
row: 406
column: 39
fix: ~
- kind: EndsInPeriod
location:
row: 405
row: 410
column: 4
end_location:
row: 405
row: 410
column: 24
fix: ~
- kind: EndsInPeriod
location:
row: 411
row: 416
column: 4
end_location:
row: 411
row: 416
column: 24
fix: ~
- kind: EndsInPeriod
location:
row: 417
row: 422
column: 34
end_location:
row: 417
row: 422
column: 49
fix: ~
- kind: EndsInPeriod
location:
row: 424
row: 429
column: 48
end_location:
row: 424
row: 429
column: 63
fix: ~
- kind: EndsInPeriod
location:
row: 465
column: 4
end_location:
row: 465
column: 24
fix: ~
- kind: EndsInPeriod
location:
row: 470
@@ -76,34 +68,42 @@ expression: checks
fix: ~
- kind: EndsInPeriod
location:
row: 482
row: 480
column: 4
end_location:
row: 482
row: 480
column: 24
fix: ~
- kind: EndsInPeriod
location:
row: 504
row: 487
column: 4
end_location:
row: 504
row: 487
column: 24
fix: ~
- kind: EndsInPeriod
location:
row: 509
column: 4
end_location:
row: 509
column: 34
fix: ~
- kind: EndsInPeriod
location:
row: 509
row: 514
column: 4
end_location:
row: 509
row: 514
column: 33
fix: ~
- kind: EndsInPeriod
location:
row: 515
row: 520
column: 4
end_location:
row: 515
row: 520
column: 32
fix: ~

View File

@@ -4,10 +4,10 @@ expression: checks
---
- kind: NoSignature
location:
row: 373
row: 378
column: 4
end_location:
row: 373
row: 378
column: 30
fix: ~

View File

@@ -4,60 +4,52 @@ expression: checks
---
- kind: EndsInPunctuation
location:
row: 350
row: 355
column: 4
end_location:
row: 350
row: 355
column: 17
fix: ~
- kind: EndsInPunctuation
location:
row: 401
row: 406
column: 24
end_location:
row: 401
row: 406
column: 39
fix: ~
- kind: EndsInPunctuation
location:
row: 405
row: 410
column: 4
end_location:
row: 405
row: 410
column: 24
fix: ~
- kind: EndsInPunctuation
location:
row: 411
row: 416
column: 4
end_location:
row: 411
row: 416
column: 24
fix: ~
- kind: EndsInPunctuation
location:
row: 417
row: 422
column: 34
end_location:
row: 417
row: 422
column: 49
fix: ~
- kind: EndsInPunctuation
location:
row: 424
row: 429
column: 48
end_location:
row: 424
row: 429
column: 63
fix: ~
- kind: EndsInPunctuation
location:
row: 465
column: 4
end_location:
row: 465
column: 24
fix: ~
- kind: EndsInPunctuation
location:
row: 470
@@ -76,26 +68,34 @@ expression: checks
fix: ~
- kind: EndsInPunctuation
location:
row: 482
row: 480
column: 4
end_location:
row: 482
row: 480
column: 24
fix: ~
- kind: EndsInPunctuation
location:
row: 504
row: 487
column: 4
end_location:
row: 504
row: 487
column: 24
fix: ~
- kind: EndsInPunctuation
location:
row: 509
column: 4
end_location:
row: 509
column: 34
fix: ~
- kind: EndsInPunctuation
location:
row: 515
row: 520
column: 4
end_location:
row: 515
row: 520
column: 32
fix: ~

View File

@@ -4,26 +4,26 @@ expression: checks
---
- kind: SkipDocstring
location:
row: 33
row: 34
column: 4
end_location:
row: 37
row: 38
column: 4
fix: ~
- kind: SkipDocstring
location:
row: 85
row: 90
column: 4
end_location:
row: 89
row: 94
column: 4
fix: ~
- kind: SkipDocstring
location:
row: 105
row: 110
column: 0
end_location:
row: 110
row: 115
column: 0
fix: ~

View File

@@ -4,26 +4,26 @@ expression: checks
---
- kind: NonEmpty
location:
row: 19
row: 20
column: 8
end_location:
row: 19
row: 20
column: 14
fix: ~
- kind: NonEmpty
location:
row: 69
row: 74
column: 4
end_location:
row: 69
row: 74
column: 11
fix: ~
- kind: NonEmpty
location:
row: 75
row: 80
column: 8
end_location:
row: 75
row: 80
column: 10
fix: ~

View File

@@ -0,0 +1,85 @@
---
source: src/linter.rs
expression: checks
---
- kind: CommentedOutCode
location:
row: 1
column: 0
end_location:
row: 1
column: 10
fix:
patch:
content: ""
location:
row: 1
column: 0
end_location:
row: 2
column: 0
- kind: CommentedOutCode
location:
row: 2
column: 0
end_location:
row: 2
column: 22
fix:
patch:
content: ""
location:
row: 2
column: 0
end_location:
row: 3
column: 0
- kind: CommentedOutCode
location:
row: 3
column: 0
end_location:
row: 3
column: 6
fix:
patch:
content: ""
location:
row: 3
column: 0
end_location:
row: 4
column: 0
- kind: CommentedOutCode
location:
row: 5
column: 0
end_location:
row: 5
column: 13
fix:
patch:
content: ""
location:
row: 5
column: 0
end_location:
row: 6
column: 0
- kind: CommentedOutCode
location:
row: 12
column: 4
end_location:
row: 12
column: 16
fix:
patch:
content: ""
location:
row: 12
column: 0
end_location:
row: 13
column: 0

View File

@@ -4,14 +4,14 @@ expression: checks
---
- kind:
UnusedImport:
- - functools
- functools
- false
location:
row: 2
column: 0
column: 7
end_location:
row: 2
column: 20
column: 16
fix:
patch:
content: import os
@@ -23,14 +23,14 @@ expression: checks
column: 20
- kind:
UnusedImport:
- - collections.OrderedDict
- collections.OrderedDict
- false
location:
row: 4
column: 0
row: 6
column: 4
end_location:
row: 8
column: 1
row: 6
column: 15
fix:
patch:
content: "from collections import (\n Counter,\n namedtuple,\n)"
@@ -42,11 +42,11 @@ expression: checks
column: 1
- kind:
UnusedImport:
- - logging.handlers
- logging.handlers
- false
location:
row: 12
column: 0
column: 7
end_location:
row: 12
column: 23
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - shelve
- shelve
- false
location:
row: 32
column: 4
column: 11
end_location:
row: 32
column: 17
@@ -80,11 +80,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - importlib
- importlib
- false
location:
row: 33
column: 4
column: 11
end_location:
row: 33
column: 20
@@ -99,11 +99,11 @@ expression: checks
column: 20
- kind:
UnusedImport:
- - pathlib
- pathlib
- false
location:
row: 37
column: 4
column: 11
end_location:
row: 37
column: 18
@@ -118,11 +118,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - pickle
- pickle
- false
location:
row: 52
column: 8
column: 15
end_location:
row: 52
column: 21

View File

@@ -4,11 +4,11 @@ expression: checks
---
- kind:
UnusedImport:
- - a.b.c
- a.b.c
- false
location:
row: 2
column: 0
column: 16
end_location:
row: 2
column: 17
@@ -23,11 +23,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - d.e.f
- d.e.f
- false
location:
row: 3
column: 0
column: 16
end_location:
row: 3
column: 22
@@ -42,11 +42,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - h.i
- h.i
- false
location:
row: 4
column: 0
column: 7
end_location:
row: 4
column: 10
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - j.k
- j.k
- false
location:
row: 5
column: 0
column: 7
end_location:
row: 5
column: 15

View File

@@ -4,11 +4,11 @@ expression: checks
---
- kind:
UnusedImport:
- - background.BackgroundTasks
- background.BackgroundTasks
- false
location:
row: 7
column: 0
column: 24
end_location:
row: 7
column: 39
@@ -23,11 +23,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - datastructures.UploadFile
- datastructures.UploadFile
- false
location:
row: 10
column: 0
column: 28
end_location:
row: 10
column: 52
@@ -42,11 +42,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - background
- background
- false
location:
row: 17
column: 0
column: 7
end_location:
row: 17
column: 17
@@ -61,11 +61,11 @@ expression: checks
column: 0
- kind:
UnusedImport:
- - datastructures
- datastructures
- false
location:
row: 20
column: 0
column: 7
end_location:
row: 20
column: 35

View File

@@ -6,7 +6,7 @@ expression: checks
FutureFeatureNotDefined: non_existent_feature
location:
row: 2
column: 0
column: 23
end_location:
row: 2
column: 43

View File

@@ -0,0 +1,14 @@
---
source: src/linter.rs
expression: checks
---
- kind:
PercentFormatInvalidFormat: incomplete format
location:
row: 1
column: 9
end_location:
row: 1
column: 25
fix: ~

View File

@@ -0,0 +1,61 @@
---
source: src/linter.rs
expression: checks
---
- kind: PercentFormatExpectedMapping
location:
row: 6
column: 10
end_location:
row: 6
column: 19
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 7
column: 10
end_location:
row: 7
column: 20
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 8
column: 10
end_location:
row: 8
column: 19
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 9
column: 10
end_location:
row: 9
column: 22
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 11
column: 10
end_location:
row: 11
column: 37
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 12
column: 10
end_location:
row: 12
column: 37
fix: ~
- kind: PercentFormatExpectedMapping
location:
row: 13
column: 10
end_location:
row: 13
column: 37
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: PercentFormatExpectedMapping
location:
row: 9
column: 10
end_location:
row: 9
column: 21
fix: ~

View File

@@ -0,0 +1,29 @@
---
source: src/linter.rs
expression: checks
---
- kind: PercentFormatExpectedSequence
location:
row: 17
column: 8
end_location:
row: 17
column: 24
fix: ~
- kind: PercentFormatExpectedSequence
location:
row: 18
column: 8
end_location:
row: 18
column: 28
fix: ~
- kind: PercentFormatExpectedSequence
location:
row: 23
column: 8
end_location:
row: 23
column: 42
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: PercentFormatExpectedSequence
location:
row: 10
column: 8
end_location:
row: 10
column: 20
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind:
PercentFormatExtraNamedArguments:
- b
location:
row: 3
column: 14
end_location:
row: 3
column: 34
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind:
PercentFormatExtraNamedArguments:
- baz
location:
row: 8
column: 10
end_location:
row: 8
column: 32
fix: ~

View File

@@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

Some files were not shown because too many files have changed in this diff Show More