From 82b0b7941a45238299d0ee0e66992c7e628c8218 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 28 Nov 2022 20:54:33 -0500 Subject: [PATCH] Implement eradicate (#947) --- LICENSE | 25 ++ README.md | 64 ++-- flake8_to_ruff/src/plugin.rs | 95 +++--- resources/test/fixtures/ERA001.py | 13 + src/check_lines.rs | 4 +- src/check_tokens.rs | 14 +- src/checks.rs | 308 +++++++++--------- src/checks_gen.rs | 62 ++++ src/eradicate/checks.rs | 51 +++ src/eradicate/detection.rs | 223 +++++++++++++ src/eradicate/mod.rs | 2 + src/lib.rs | 1 + src/linter.rs | 39 +-- src/noqa.rs | 4 +- src/pydocstyle/plugins.rs | 2 +- ...ruff__linter__tests__ERA001_ERA001.py.snap | 85 +++++ 16 files changed, 755 insertions(+), 237 deletions(-) create mode 100644 resources/test/fixtures/ERA001.py create mode 100644 src/eradicate/checks.rs create mode 100644 src/eradicate/detection.rs create mode 100644 src/eradicate/mod.rs create mode 100644 src/snapshots/ruff__linter__tests__ERA001_ERA001.py.snap diff --git a/LICENSE b/LICENSE index a4b8c442ea..9de3bd6e59 100644 --- a/LICENSE +++ b/LICENSE @@ -267,6 +267,31 @@ are: 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 diff --git a/README.md b/README.md index 6dbff40ac7..0a0149896b 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ An extremely fast Python linter, written in Rust. Ruff aims to be orders of magnitude faster than alternative tools while integrating more functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/), -[`yesqa`](https://github.com/asottile/yesqa), and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) -and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times -faster than any individual tool. +[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/), +and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/) +all while executing tens or hundreds of times faster than any individual tool. Ruff is extremely actively developed and used in major open-source projects like: @@ -73,6 +73,7 @@ of [Conda](https://docs.conda.io/en/latest/): 1. [pydocstyle (D)](#pydocstyle) 1. [pyupgrade (U)](#pyupgrade) 1. [pep8-naming (N)](#pep8-naming) + 1. [eradicate (ERA)](#eradicate) 1. [flake8-bandit (S)](#flake8-bandit) 1. [flake8-comprehensions (C)](#flake8-comprehensions) 1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap) @@ -533,6 +534,14 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyP | N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | | | N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | | +### eradicate + +For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| ERA001 | CommentedCode | Found commented-out code | 🛠 | + ### flake8-bandit For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on PyPI. @@ -854,27 +863,29 @@ plugins, (2) alongside Black, and (3) on Python 3 code. Under those conditions, Ruff implements every rule in Flake8, with the exception of `F811`. Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools -natively, including: +natively, including: +- [`isort`](https://pypi.org/project/isort/) - [`pydocstyle`](https://pypi.org/project/pydocstyle/) - [`pep8-naming`](https://pypi.org/project/pep8-naming/) -- [`yesqa`](https://github.com/asottile/yesqa) -- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) -- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) -- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) -- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) -- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) -- [`flake8-super`](https://pypi.org/project/flake8-super/) -- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3) -- [`flake8-print`](https://pypi.org/project/flake8-print/) -- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) +- [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) - [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40) -- [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/) - [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/) +- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) +- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) +- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) +- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) +- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) +- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/) +- [`flake8-print`](https://pypi.org/project/flake8-print/) +- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) +- [`flake8-super`](https://pypi.org/project/flake8-super/) +- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3) - [`mccabe`](https://pypi.org/project/mccabe/) -- [`isort`](https://pypi.org/project/isort/) +- [`yesqa`](https://github.com/asottile/yesqa) +- [`eradicate`](https://pypi.org/project/eradicate/) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33) - [`autoflake`](https://pypi.org/project/autoflake/) (1/7) @@ -891,20 +902,21 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`pydocstyle`](https://pypi.org/project/pydocstyle/) - [`pep8-naming`](https://pypi.org/project/pep8-naming/) -- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) -- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) -- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) -- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) -- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) -- [`flake8-super`](https://pypi.org/project/flake8-super/) -- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3) -- [`flake8-print`](https://pypi.org/project/flake8-print/) -- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) +- [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) - [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40) -- [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`flake8-blind-except`](https://pypi.org/project/flake8-blind-except/) - [`flake8-boolean-trap`](https://pypi.org/project/flake8-boolean-trap/) +- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) +- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) +- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) +- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) +- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) +- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/) +- [`flake8-print`](https://pypi.org/project/flake8-print/) +- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) +- [`flake8-super`](https://pypi.org/project/flake8-super/) +- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3) - [`mccabe`](https://pypi.org/project/mccabe/) Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa), diff --git a/flake8_to_ruff/src/plugin.rs b/flake8_to_ruff/src/plugin.rs index 7c0262c798..41451447d6 100644 --- a/flake8_to_ruff/src/plugin.rs +++ b/flake8_to_ruff/src/plugin.rs @@ -6,18 +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, } @@ -27,17 +28,18 @@ impl FromStr for Plugin { fn from_str(string: &str) -> Result { 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), @@ -49,17 +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::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, @@ -68,7 +71,9 @@ impl Plugin { pub fn select(&self, flake8: &HashMap>) -> Vec { 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], @@ -89,11 +94,10 @@ impl Plugin { // Default to PEP8. DocstringConvention::PEP8.select() } - Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25], + 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], @@ -281,31 +285,6 @@ pub fn infer_plugins_from_options(flake8: &HashMap>) -> 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); @@ -331,6 +310,41 @@ pub fn infer_plugins_from_options(flake8: &HashMap>) -> 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); @@ -364,17 +378,18 @@ pub fn infer_plugins_from_options(flake8: &HashMap>) -> V /// `flake8-annotations` is active. pub fn infer_plugins_from_codes(codes: &BTreeSet) -> Vec { [ + 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, ] diff --git a/resources/test/fixtures/ERA001.py b/resources/test/fixtures/ERA001.py new file mode 100644 index 0000000000..e4ee9bfad7 --- /dev/null +++ b/resources/test/fixtures/ERA001.py @@ -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 diff --git a/src/check_lines.rs b/src/check_lines.rs index f5ac78b6ff..2d55f24297 100644 --- a/src/check_lines.rs +++ b/src/check_lines.rs @@ -14,9 +14,9 @@ use crate::settings::Settings; // Regex from PEP263 static CODING_COMMENT_REGEX: Lazy = - 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 = Lazy::new(|| Regex::new(r"^https?://\S+$").expect("Invalid regex")); +static URL_REGEX: Lazy = 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 { diff --git a/src/check_tokens.rs b/src/check_tokens.rs index 68d62bb926..7c242b509d 100644 --- a/src/check_tokens.rs +++ b/src/check_tokens.rs @@ -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 { .. }) { diff --git a/src/checks.rs b/src/checks.rs index 10da8cd280..5824ee34ac 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -254,6 +254,8 @@ pub enum CheckCode { N818, // isort I001, + // eradicate + ERA001, // flake8-bandit S101, S102, @@ -282,6 +284,7 @@ pub enum CheckCategory { Pydocstyle, Pyupgrade, PEP8Naming, + Eradicate, Flake8Bandit, Flake8Comprehensions, Flake8Debugger, @@ -302,69 +305,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::Flake8Debugger => "flake8-debugger", - CheckCategory::Flake8TidyImports => "flake8-tidy-imports", 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::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::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::Flake8BooleanTrap => { + Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/") } 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::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"), - CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"), - 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::Isort => Some("https://pypi.org/project/isort/5.10.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::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, } } } @@ -611,6 +616,8 @@ pub enum CheckKind { ErrorSuffixOnExceptionName(String), // isort UnsortedImports, + // eradicate + CommentedOutCode, // flake8-bandit AssertUsed, ExecUsed, @@ -641,7 +648,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 @@ -925,6 +933,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, @@ -950,67 +960,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::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::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, @@ -1055,49 +1018,7 @@ impl CheckCode { CheckCode::C415 => CheckCategory::Flake8Comprehensions, CheckCode::C416 => CheckCategory::Flake8Comprehensions, CheckCode::C417 => CheckCategory::Flake8Comprehensions, - CheckCode::T100 => CheckCategory::Flake8Debugger, - 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, @@ -1142,6 +1063,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, @@ -1157,22 +1141,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, } } } @@ -1405,6 +1416,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, @@ -2117,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(), @@ -2221,6 +2236,7 @@ impl CheckKind { | CheckKind::BlankLineAfterSummary | CheckKind::BlankLineBeforeSection(..) | CheckKind::CapitalizeSectionName(..) + | CheckKind::CommentedOutCode | CheckKind::ConvertExitToSysExit | CheckKind::ConvertNamedTupleFunctionalToClass(..) | CheckKind::ConvertTypedDictFunctionalToClass(..) diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 49f517ad24..cec40003a3 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -176,6 +176,10 @@ pub enum CheckCodePrefix { E902, E99, E999, + ERA, + ERA8, + ERA80, + 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, @@ -853,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::ERA8 => vec![CheckCode::ERA001], + CheckCodePrefix::ERA80 => vec![CheckCode::ERA001], + CheckCodePrefix::ERA001 => vec![CheckCode::ERA001], CheckCodePrefix::F => vec![ CheckCode::F401, CheckCode::F402, @@ -923,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, @@ -930,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, @@ -1455,6 +1502,10 @@ impl CheckCodePrefix { CheckCodePrefix::E902 => PrefixSpecificity::Explicit, CheckCodePrefix::E99 => PrefixSpecificity::Tens, CheckCodePrefix::E999 => PrefixSpecificity::Explicit, + CheckCodePrefix::ERA => PrefixSpecificity::Category, + CheckCodePrefix::ERA8 => PrefixSpecificity::Hundreds, + CheckCodePrefix::ERA80 => PrefixSpecificity::Tens, + CheckCodePrefix::ERA001 => PrefixSpecificity::Explicit, CheckCodePrefix::F => PrefixSpecificity::Category, CheckCodePrefix::F4 => PrefixSpecificity::Hundreds, CheckCodePrefix::F40 => PrefixSpecificity::Tens, @@ -1466,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, @@ -1630,6 +1691,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[ CheckCodePrefix::C, CheckCodePrefix::D, CheckCodePrefix::E, + CheckCodePrefix::ERA, CheckCodePrefix::F, CheckCodePrefix::FBT, CheckCodePrefix::I, diff --git a/src/eradicate/checks.rs b/src/eradicate/checks.rs new file mode 100644 index 0000000000..109360c648 --- /dev/null +++ b/src/eradicate/checks.rs @@ -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 { + 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 + } +} diff --git a/src/eradicate/detection.rs b/src/eradicate/detection.rs new file mode 100644 index 0000000000..000cec2726 --- /dev/null +++ b/src/eradicate/detection.rs @@ -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 = 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 = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap()); +static CODE_INDICATORS: &[&str] = &[ + "(", ")", "[", "]", "{", "}", ":", "=", "%", "print", "return", "break", "continue", "import", +]; +static CODE_KEYWORDS: Lazy> = 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 = + Lazy::new(|| Regex::new(r"^.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)").unwrap()); +static HASH_NUMBER: Lazy = Lazy::new(|| Regex::new(r"#\d").unwrap()); +static MULTILINE_ASSIGNMENT_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^\s*\w+\s*=.*[(\[{]$").unwrap()); +static PARTIAL_DICTIONARY_REGEX: Lazy = + Lazy::new(|| Regex::new(r#"^\s*['"]\w+['"]\s*:.+[,{]\s*$"#).unwrap()); +static PRINT_RETURN_REGEX: Lazy = 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, "").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")); + } +} diff --git a/src/eradicate/mod.rs b/src/eradicate/mod.rs new file mode 100644 index 0000000000..d602ab1fb1 --- /dev/null +++ b/src/eradicate/mod.rs @@ -0,0 +1,2 @@ +pub mod checks; +pub mod detection; diff --git a/src/lib.rs b/src/lib.rs index 7b2ed66aa5..4c72f9eb05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/linter.rs b/src/linter.rs index 37b5019390..b079a30804 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -487,9 +487,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")] @@ -505,6 +505,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")] @@ -519,14 +520,14 @@ mod tests { #[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("F50x.py"); "F502_0")] #[test_case(CheckCode::F502, Path::new("F502.py"); "F502_1")] - #[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")] + #[test_case(CheckCode::F502, Path::new("F50x.py"); "F502_0")] #[test_case(CheckCode::F503, Path::new("F503.py"); "F503_1")] - #[test_case(CheckCode::F504, Path::new("F50x.py"); "F504_0")] + #[test_case(CheckCode::F503, Path::new("F50x.py"); "F503_0")] #[test_case(CheckCode::F504, Path::new("F504.py"); "F504_1")] - #[test_case(CheckCode::F505, Path::new("F50x.py"); "F505_0")] + #[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")] @@ -561,6 +562,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")] @@ -576,6 +580,16 @@ 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")] @@ -609,16 +623,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")] @@ -629,9 +633,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( diff --git a/src/noqa.rs b/src/noqa.rs index c59f48f08e..ca832deaeb 100644 --- a/src/noqa.rs +++ b/src/noqa.rs @@ -13,9 +13,9 @@ static NO_QA_REGEX: Lazy = Lazy::new(|| { Regex::new( r"(?P\s*)(?P(?i:# noqa)(?::\s?(?P([A-Z]+[0-9]+(?:[,\s]+)?)+))?)", ) - .expect("Invalid regex") + .unwrap() }); -static SPLIT_COMMA_REGEX: Lazy = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex")); +static SPLIT_COMMA_REGEX: Lazy = Lazy::new(|| Regex::new(r"[,\s]").unwrap()); #[derive(Debug)] pub enum Directive<'a> { diff --git a/src/pydocstyle/plugins.rs b/src/pydocstyle/plugins.rs index a82ab63c6f..b5aaf8c0a6 100644 --- a/src/pydocstyle/plugins.rs +++ b/src/pydocstyle/plugins.rs @@ -1360,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 = - 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 = vec![]; diff --git a/src/snapshots/ruff__linter__tests__ERA001_ERA001.py.snap b/src/snapshots/ruff__linter__tests__ERA001_ERA001.py.snap new file mode 100644 index 0000000000..1e85e38742 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__ERA001_ERA001.py.snap @@ -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 +