From b8401fd0274491f299aa6568bc37cbb907a42e6b Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:08:49 +0100 Subject: [PATCH 1/2] Add no-eval rule from pygrep-hooks --- LICENSE | 23 +++++ README.md | 21 +++-- .../test/fixtures/pygrep-hooks/PGH001_0.py | 9 ++ .../test/fixtures/pygrep-hooks/PGH001_1.py | 11 +++ ruff_dev/src/generate_rules_table.rs | 7 +- src/check_ast.rs | 7 +- src/checks.rs | 94 +++++++++++++------ src/checks_gen.rs | 13 +++ src/lib.rs | 1 + src/pygrep_hooks/checks.rs | 15 +++ src/pygrep_hooks/mod.rs | 30 ++++++ ...grep_hooks__tests__PGH001_PGH001_0.py.snap | 21 +++++ ...grep_hooks__tests__PGH001_PGH001_1.py.snap | 6 ++ 13 files changed, 218 insertions(+), 40 deletions(-) create mode 100644 resources/test/fixtures/pygrep-hooks/PGH001_0.py create mode 100644 resources/test/fixtures/pygrep-hooks/PGH001_1.py create mode 100644 src/pygrep_hooks/checks.rs create mode 100644 src/pygrep_hooks/mod.rs create mode 100644 src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_0.py.snap create mode 100644 src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_1.py.snap diff --git a/LICENSE b/LICENSE index 9de3bd6e59cb8..77cb3f108f449 100644 --- a/LICENSE +++ b/LICENSE @@ -471,6 +471,29 @@ are: SOFTWARE. """ +- pygrep-hooks, licensed as follows: + """ + Copyright (c) 2018 Anthony Sottile + + 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. + """ + - pyupgrade, licensed as follows: """ Copyright (c) 2017 Anthony Sottile diff --git a/README.md b/README.md index 403655bd7b56d..a9a969801ed0b 100644 --- a/README.md +++ b/README.md @@ -73,17 +73,18 @@ of [Conda](https://docs.conda.io/en/latest/): 1. [pycodestyle (E, W)](#pycodestyle) 1. [isort (I)](#isort) 1. [pydocstyle (D)](#pydocstyle) + 1. [pygrep-hooks (PGH)](#pygrep-hooks) 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-comprehensions (C4)](#flake8-comprehensions) 1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap) 1. [flake8-bugbear (B)](#flake8-bugbear) 1. [flake8-builtins (A)](#flake8-builtins) - 1. [flake8-debugger (T)](#flake8-debugger) + 1. [flake8-debugger (T10)](#flake8-debugger) 1. [flake8-tidy-imports (I25)](#flake8-tidy-imports) - 1. [flake8-print (T)](#flake8-print) + 1. [flake8-print (T20)](#flake8-print) 1. [flake8-quotes (Q)](#flake8-quotes) 1. [flake8-annotations (ANN)](#flake8-annotations) 1. [flake8-2020 (YTT)](#flake8-2020) @@ -498,6 +499,14 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI. | D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | | | D419 | NonEmpty | Docstring is empty | | +### pygrep-hooks + +For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| PGH001 | NoEval | No builtin `eval()` allowed | | + ### pyupgrade For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. @@ -903,6 +912,7 @@ natively, including: - [`eradicate`](https://pypi.org/project/eradicate/) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33) - [`autoflake`](https://pypi.org/project/autoflake/) (1/7) +- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10) Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8: @@ -946,8 +956,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`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), -and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33). +Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/), [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10), and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33). If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue. @@ -1261,7 +1270,7 @@ Exclusions are based on globs, and can be either: (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`). -Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify the excluded +Note that you'll typically want to use [`extend_exclude`](#extend-exclude) to modify the excluded paths. **Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]` diff --git a/resources/test/fixtures/pygrep-hooks/PGH001_0.py b/resources/test/fixtures/pygrep-hooks/PGH001_0.py new file mode 100644 index 0000000000000..eed83b81f987c --- /dev/null +++ b/resources/test/fixtures/pygrep-hooks/PGH001_0.py @@ -0,0 +1,9 @@ +from ast import literal_eval + +eval("3 + 4") + +literal_eval({1: 2}) + + +def fn() -> None: + eval("3 + 4") diff --git a/resources/test/fixtures/pygrep-hooks/PGH001_1.py b/resources/test/fixtures/pygrep-hooks/PGH001_1.py new file mode 100644 index 0000000000000..ecb3e91a3a5d5 --- /dev/null +++ b/resources/test/fixtures/pygrep-hooks/PGH001_1.py @@ -0,0 +1,11 @@ +def eval(content: str) -> None: + pass + + +eval("3 + 4") + +literal_eval({1: 2}) + + +def fn() -> None: + eval("3 + 4") diff --git a/ruff_dev/src/generate_rules_table.rs b/ruff_dev/src/generate_rules_table.rs index 02eccc294c29c..a5d186f52c643 100644 --- a/ruff_dev/src/generate_rules_table.rs +++ b/ruff_dev/src/generate_rules_table.rs @@ -28,11 +28,12 @@ pub fn main(cli: &Cli) -> Result<()> { output.push('\n'); output.push('\n'); - if let Some(url) = check_category.url() { + if let Some((url, platform)) = check_category.url() { output.push_str(&format!( - "For more, see [{}]({}) on PyPI.", + "For more, see [{}]({}) on {}.", check_category.title(), - url + url, + platform )); output.push('\n'); output.push('\n'); diff --git a/src/check_ast.rs b/src/check_ast.rs index 7331b167189c1..9147a8ef22b55 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -37,7 +37,7 @@ use crate::{ docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger, flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, - pylint, pyupgrade, rules, + pygrep_hooks, pylint, pyupgrade, rules, }; const GLOBAL_SCOPE_INDEX: usize = 0; @@ -1704,6 +1704,11 @@ where if self.settings.enabled.contains(&CheckCode::RUF101) { rules::plugins::convert_exit_to_sys_exit(self, func); } + + // Ruff + if self.settings.enabled.contains(&CheckCode::PGH001) { + pygrep_hooks::checks::no_eval(self, func); + } } ExprKind::Dict { keys, .. } => { let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601); diff --git a/src/checks.rs b/src/checks.rs index 2c2a98f708d01..91a16862dc431 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -278,6 +278,8 @@ pub enum CheckCode { RUF101, // Meta M001, + // pygrep-hooks + PGH001, } #[derive(EnumIter, Debug, PartialEq, Eq)] @@ -286,6 +288,7 @@ pub enum CheckCategory { Pycodestyle, Isort, Pydocstyle, + PygrepHooks, Pyupgrade, PEP8Naming, Eradicate, @@ -331,51 +334,73 @@ impl CheckCategory { CheckCategory::Pydocstyle => "pydocstyle", CheckCategory::Pyflakes => "Pyflakes", CheckCategory::Pylint => "Pylint", + CheckCategory::PygrepHooks => "pygrep-hooks", CheckCategory::Pyupgrade => "pyupgrade", CheckCategory::Ruff => "Ruff-specific rules", } } - pub fn url(&self) -> Option<&'static str> { + pub fn url(&self) -> Option<(&'static str, &'static str)> { match self { - 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::Eradicate => Some(("https://pypi.org/project/eradicate/2.1.0/", "PyPI")), + CheckCategory::Flake82020 => { + Some(("https://pypi.org/project/flake8-2020/1.7.0/", "PyPI")) } + CheckCategory::Flake8Annotations => { + Some(("https://pypi.org/project/flake8-annotations/2.9.1/", "PyPI")) + } + CheckCategory::Flake8Bandit => { + Some(("https://pypi.org/project/flake8-bandit/4.1.1/", "PyPI")) + } + CheckCategory::Flake8BlindExcept => Some(( + "https://pypi.org/project/flake8-blind-except/0.2.1/", + "PyPI", + )), + CheckCategory::Flake8BooleanTrap => Some(( + "https://pypi.org/project/flake8-boolean-trap/0.1.0/", + "PyPI", + )), CheckCategory::Flake8Bugbear => { - Some("https://pypi.org/project/flake8-bugbear/22.10.27/") + Some(("https://pypi.org/project/flake8-bugbear/22.10.27/", "PyPI")) } CheckCategory::Flake8Builtins => { - Some("https://pypi.org/project/flake8-builtins/2.0.1/") - } - CheckCategory::Flake8Comprehensions => { - Some("https://pypi.org/project/flake8-comprehensions/3.10.1/") + Some(("https://pypi.org/project/flake8-builtins/2.0.1/", "PyPI")) } + CheckCategory::Flake8Comprehensions => Some(( + "https://pypi.org/project/flake8-comprehensions/3.10.1/", + "PyPI", + )), CheckCategory::Flake8Debugger => { - Some("https://pypi.org/project/flake8-debugger/4.1.2/") + Some(("https://pypi.org/project/flake8-debugger/4.1.2/", "PyPI")) } - 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/", "PyPI")) } - CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"), - CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"), + CheckCategory::Flake8Quotes => { + Some(("https://pypi.org/project/flake8-quotes/3.3.1/", "PyPI")) + } + CheckCategory::Flake8TidyImports => Some(( + "https://pypi.org/project/flake8-tidy-imports/4.8.0/", + "PyPI", + )), + CheckCategory::Isort => Some(("https://pypi.org/project/isort/5.10.1/", "PyPI")), + CheckCategory::McCabe => Some(("https://pypi.org/project/mccabe/0.7.0/", "PyPI")), 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::Pylint => Some("https://pypi.org/project/pylint/2.15.7/"), - CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"), + CheckCategory::PEP8Naming => { + Some(("https://pypi.org/project/pep8-naming/0.13.2/", "PyPI")) + } + CheckCategory::Pycodestyle => { + Some(("https://pypi.org/project/pycodestyle/2.9.1/", "PyPI")) + } + CheckCategory::Pydocstyle => { + Some(("https://pypi.org/project/pydocstyle/6.1.1/", "PyPI")) + } + CheckCategory::Pyflakes => Some(("https://pypi.org/project/pyflakes/2.5.0/", "PyPI")), + CheckCategory::Pylint => Some(("https://pypi.org/project/pylint/2.15.7/", "PyPI")), + CheckCategory::PygrepHooks => { + Some(("https://github.com/pre-commit/pygrep-hooks", "GitHub")) + } + CheckCategory::Pyupgrade => Some(("https://pypi.org/project/pyupgrade/3.2.0/", "PyPI")), CheckCategory::Ruff => None, } } @@ -664,6 +689,8 @@ pub enum CheckKind { ConvertExitToSysExit, // Meta UnusedNOQA(Option>), + // pygrep-hooks + NoEval, } impl CheckCode { @@ -982,6 +1009,8 @@ impl CheckCode { CheckCode::RUF101 => CheckKind::ConvertExitToSysExit, // Meta CheckCode::M001 => CheckKind::UnusedNOQA(None), + // pygrep-hooks + CheckCode::PGH001 => CheckKind::NoEval, } } @@ -1169,6 +1198,7 @@ impl CheckCode { CheckCode::N816 => CheckCategory::PEP8Naming, CheckCode::N817 => CheckCategory::PEP8Naming, CheckCode::N818 => CheckCategory::PEP8Naming, + CheckCode::PGH001 => CheckCategory::PygrepHooks, CheckCode::PLE1142 => CheckCategory::Pylint, CheckCode::Q000 => CheckCategory::Flake8Quotes, CheckCode::Q001 => CheckCategory::Flake8Quotes, @@ -1469,6 +1499,8 @@ impl CheckKind { CheckKind::ConvertExitToSysExit => &CheckCode::RUF101, // Meta CheckKind::UnusedNOQA(_) => &CheckCode::M001, + // pygrep-hooks + CheckKind::NoEval => &CheckCode::PGH001, } } @@ -2236,6 +2268,8 @@ impl CheckKind { format!("Unused `noqa` directive for: {codes}") } }, + // pygrep-hooks + CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), } } diff --git a/src/checks_gen.rs b/src/checks_gen.rs index efe3723d53c25..9768cfee0bb1e 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -278,6 +278,10 @@ pub enum CheckCodePrefix { N816, N817, N818, + PGH, + PGH0, + PGH00, + PGH001, PLE, PLE1, PLE11, @@ -1148,6 +1152,10 @@ impl CheckCodePrefix { CheckCodePrefix::N816 => vec![CheckCode::N816], CheckCodePrefix::N817 => vec![CheckCode::N817], CheckCodePrefix::N818 => vec![CheckCode::N818], + CheckCodePrefix::PGH => vec![CheckCode::PGH001], + CheckCodePrefix::PGH0 => vec![CheckCode::PGH001], + CheckCodePrefix::PGH00 => vec![CheckCode::PGH001], + CheckCodePrefix::PGH001 => vec![CheckCode::PGH001], CheckCodePrefix::PLE => vec![CheckCode::PLE1142], CheckCodePrefix::PLE1 => vec![CheckCode::PLE1142], CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142], @@ -1615,6 +1623,10 @@ impl CheckCodePrefix { CheckCodePrefix::N816 => SuffixLength::Three, CheckCodePrefix::N817 => SuffixLength::Three, CheckCodePrefix::N818 => SuffixLength::Three, + CheckCodePrefix::PGH => SuffixLength::Zero, + CheckCodePrefix::PGH0 => SuffixLength::One, + CheckCodePrefix::PGH00 => SuffixLength::Two, + CheckCodePrefix::PGH001 => SuffixLength::Three, CheckCodePrefix::PLE => SuffixLength::Zero, CheckCodePrefix::PLE1 => SuffixLength::One, CheckCodePrefix::PLE11 => SuffixLength::Two, @@ -1713,6 +1725,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[ CheckCodePrefix::I, CheckCodePrefix::M, CheckCodePrefix::N, + CheckCodePrefix::PGH, CheckCodePrefix::PLE, CheckCodePrefix::Q, CheckCodePrefix::RUF, diff --git a/src/lib.rs b/src/lib.rs index db2cc9cc6494f..fcb9552bf5feb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ pub mod printer; mod pycodestyle; mod pydocstyle; mod pyflakes; +mod pygrep_hooks; mod pylint; mod python; mod pyupgrade; diff --git a/src/pygrep_hooks/checks.rs b/src/pygrep_hooks/checks.rs new file mode 100644 index 0000000000000..c2a32d2b07da6 --- /dev/null +++ b/src/pygrep_hooks/checks.rs @@ -0,0 +1,15 @@ +use rustpython_ast::{Expr, ExprKind}; + +use crate::ast::types::Range; +use crate::check_ast::Checker; +use crate::checks::{Check, CheckKind}; + +pub fn no_eval(checker: &mut Checker, func: &Expr) { + if let ExprKind::Name { id, .. } = &func.node { + if id == "eval" { + if checker.is_builtin("eval") { + checker.add_check(Check::new(CheckKind::NoEval, Range::from_located(func))); + } + } + } +} diff --git a/src/pygrep_hooks/mod.rs b/src/pygrep_hooks/mod.rs new file mode 100644 index 0000000000000..5955c25d0574b --- /dev/null +++ b/src/pygrep_hooks/mod.rs @@ -0,0 +1,30 @@ +pub mod checks; + +#[cfg(test)] +mod tests { + use std::convert::AsRef; + use std::path::Path; + + use anyhow::Result; + use test_case::test_case; + + use crate::checks::CheckCode; + use crate::linter::test_path; + use crate::settings; + + #[test_case(CheckCode::PGH001, Path::new("PGH001_0.py"); "PGH001_0")] + #[test_case(CheckCode::PGH001, Path::new("PGH001_1.py"); "PGH001_1")] + fn checks(check_code: CheckCode, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); + let mut checks = test_path( + Path::new("./resources/test/fixtures/pygrep-hooks") + .join(path) + .as_path(), + &settings::Settings::for_rule(check_code), + true, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } +} diff --git a/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_0.py.snap b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_0.py.snap new file mode 100644 index 0000000000000..7f05ee7e7d76d --- /dev/null +++ b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_0.py.snap @@ -0,0 +1,21 @@ +--- +source: src/pygrep_hooks/mod.rs +expression: checks +--- +- kind: NoEval + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 4 + fix: ~ +- kind: NoEval + location: + row: 9 + column: 4 + end_location: + row: 9 + column: 8 + fix: ~ + diff --git a/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_1.py.snap b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_1.py.snap new file mode 100644 index 0000000000000..68aa191216b4e --- /dev/null +++ b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH001_PGH001_1.py.snap @@ -0,0 +1,6 @@ +--- +source: src/pygrep_hooks/mod.rs +expression: checks +--- +[] + From 0681d230a792230165d0f06909f752ecca70a4b8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 2 Dec 2022 12:58:10 -0500 Subject: [PATCH 2/2] Use enum; rearrange sections --- README.md | 26 +++++---- src/check_ast.rs | 10 ++-- src/checks.rs | 146 +++++++++++++++++++++++++++++------------------ 3 files changed, 112 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index a9a969801ed0b..1a4931cde9cb3 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,6 @@ of [Conda](https://docs.conda.io/en/latest/): 1. [pycodestyle (E, W)](#pycodestyle) 1. [isort (I)](#isort) 1. [pydocstyle (D)](#pydocstyle) - 1. [pygrep-hooks (PGH)](#pygrep-hooks) 1. [pyupgrade (U)](#pyupgrade) 1. [pep8-naming (N)](#pep8-naming) 1. [eradicate (ERA)](#eradicate) @@ -90,6 +89,8 @@ of [Conda](https://docs.conda.io/en/latest/): 1. [flake8-2020 (YTT)](#flake8-2020) 1. [flake8-blind-except (BLE)](#flake8-blind-except) 1. [mccabe (C90)](#mccabe) + 1. [pygrep-hooks (PGH)](#pygrep-hooks) + 1. [Pylint (PL)](#pylint) 1. [Ruff-specific rules (RUF)](#ruff-specific-rules) 1. [Meta rules (M)](#meta-rules) 1. [Editor Integrations](#editor-integrations) @@ -499,14 +500,6 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI. | D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | | | D419 | NonEmpty | Docstring is empty | | -### pygrep-hooks - -For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub. - -| Code | Name | Message | Fix | -| ---- | ---- | ------- | --- | -| PGH001 | NoEval | No builtin `eval()` allowed | | - ### pyupgrade For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI. @@ -735,6 +728,14 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI. | ---- | ---- | ------- | --- | | C901 | FunctionIsTooComplex | `...` is too complex (10) | | +### pygrep-hooks + +For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| PGH001 | NoEval | No builtin `eval()` allowed | | + ### Pylint For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI. @@ -911,8 +912,8 @@ natively, including: - [`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) - [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10) +- [`autoflake`](https://pypi.org/project/autoflake/) (1/7) Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8: @@ -956,7 +957,10 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`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), [`eradicate`](https://pypi.org/project/eradicate/), [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10), and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33). +Ruff can also replace [`isort`](https://pypi.org/project/isort/), +[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/), +[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10), and a subset of the rules +implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33). If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue. diff --git a/src/check_ast.rs b/src/check_ast.rs index 9147a8ef22b55..b123c17847060 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -1700,14 +1700,14 @@ where } } - // Ruff - if self.settings.enabled.contains(&CheckCode::RUF101) { - rules::plugins::convert_exit_to_sys_exit(self, func); + // pygrep-hooks + if self.settings.enabled.contains(&CheckCode::PGH001) { + pygrep_hooks::checks::no_eval(self, func); } // Ruff - if self.settings.enabled.contains(&CheckCode::PGH001) { - pygrep_hooks::checks::no_eval(self, func); + if self.settings.enabled.contains(&CheckCode::RUF101) { + rules::plugins::convert_exit_to_sys_exit(self, func); } } ExprKind::Dict { keys, .. } => { diff --git a/src/checks.rs b/src/checks.rs index 91a16862dc431..3d0e867415c66 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -288,7 +288,6 @@ pub enum CheckCategory { Pycodestyle, Isort, Pydocstyle, - PygrepHooks, Pyupgrade, PEP8Naming, Eradicate, @@ -305,11 +304,26 @@ pub enum CheckCategory { Flake82020, Flake8BlindExcept, McCabe, + PygrepHooks, Pylint, Ruff, Meta, } +pub enum Platform { + PyPI, + GitHub, +} + +impl fmt::Display for Platform { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Platform::PyPI => fmt.write_str("PyPI"), + Platform::GitHub => fmt.write_str("GitHub"), + } + } +} + impl CheckCategory { pub fn title(&self) -> &'static str { match self { @@ -340,67 +354,91 @@ impl CheckCategory { } } - pub fn url(&self) -> Option<(&'static str, &'static str)> { + pub fn url(&self) -> Option<(&'static str, &'static Platform)> { match self { - CheckCategory::Eradicate => Some(("https://pypi.org/project/eradicate/2.1.0/", "PyPI")), - CheckCategory::Flake82020 => { - Some(("https://pypi.org/project/flake8-2020/1.7.0/", "PyPI")) - } - CheckCategory::Flake8Annotations => { - Some(("https://pypi.org/project/flake8-annotations/2.9.1/", "PyPI")) - } - CheckCategory::Flake8Bandit => { - Some(("https://pypi.org/project/flake8-bandit/4.1.1/", "PyPI")) + CheckCategory::Eradicate => { + Some(("https://pypi.org/project/eradicate/2.1.0/", &Platform::PyPI)) } + CheckCategory::Flake82020 => Some(( + "https://pypi.org/project/flake8-2020/1.7.0/", + &Platform::PyPI, + )), + CheckCategory::Flake8Annotations => Some(( + "https://pypi.org/project/flake8-annotations/2.9.1/", + &Platform::PyPI, + )), + CheckCategory::Flake8Bandit => Some(( + "https://pypi.org/project/flake8-bandit/4.1.1/", + &Platform::PyPI, + )), CheckCategory::Flake8BlindExcept => Some(( "https://pypi.org/project/flake8-blind-except/0.2.1/", - "PyPI", + &Platform::PyPI, )), CheckCategory::Flake8BooleanTrap => Some(( "https://pypi.org/project/flake8-boolean-trap/0.1.0/", - "PyPI", + &Platform::PyPI, + )), + CheckCategory::Flake8Bugbear => Some(( + "https://pypi.org/project/flake8-bugbear/22.10.27/", + &Platform::PyPI, + )), + CheckCategory::Flake8Builtins => Some(( + "https://pypi.org/project/flake8-builtins/2.0.1/", + &Platform::PyPI, )), - CheckCategory::Flake8Bugbear => { - Some(("https://pypi.org/project/flake8-bugbear/22.10.27/", "PyPI")) - } - CheckCategory::Flake8Builtins => { - Some(("https://pypi.org/project/flake8-builtins/2.0.1/", "PyPI")) - } CheckCategory::Flake8Comprehensions => Some(( "https://pypi.org/project/flake8-comprehensions/3.10.1/", - "PyPI", + &Platform::PyPI, + )), + CheckCategory::Flake8Debugger => Some(( + "https://pypi.org/project/flake8-debugger/4.1.2/", + &Platform::PyPI, + )), + CheckCategory::Flake8Print => Some(( + "https://pypi.org/project/flake8-print/5.0.0/", + &Platform::PyPI, + )), + CheckCategory::Flake8Quotes => Some(( + "https://pypi.org/project/flake8-quotes/3.3.1/", + &Platform::PyPI, )), - CheckCategory::Flake8Debugger => { - Some(("https://pypi.org/project/flake8-debugger/4.1.2/", "PyPI")) - } - CheckCategory::Flake8Print => { - Some(("https://pypi.org/project/flake8-print/5.0.0/", "PyPI")) - } - CheckCategory::Flake8Quotes => { - Some(("https://pypi.org/project/flake8-quotes/3.3.1/", "PyPI")) - } CheckCategory::Flake8TidyImports => Some(( "https://pypi.org/project/flake8-tidy-imports/4.8.0/", - "PyPI", + &Platform::PyPI, )), - CheckCategory::Isort => Some(("https://pypi.org/project/isort/5.10.1/", "PyPI")), - CheckCategory::McCabe => Some(("https://pypi.org/project/mccabe/0.7.0/", "PyPI")), - CheckCategory::Meta => None, - CheckCategory::PEP8Naming => { - Some(("https://pypi.org/project/pep8-naming/0.13.2/", "PyPI")) + CheckCategory::Isort => { + Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI)) + } + CheckCategory::McCabe => { + Some(("https://pypi.org/project/mccabe/0.7.0/", &Platform::PyPI)) } - CheckCategory::Pycodestyle => { - Some(("https://pypi.org/project/pycodestyle/2.9.1/", "PyPI")) + CheckCategory::Meta => None, + CheckCategory::PEP8Naming => Some(( + "https://pypi.org/project/pep8-naming/0.13.2/", + &Platform::PyPI, + )), + CheckCategory::Pycodestyle => Some(( + "https://pypi.org/project/pycodestyle/2.9.1/", + &Platform::PyPI, + )), + CheckCategory::Pydocstyle => Some(( + "https://pypi.org/project/pydocstyle/6.1.1/", + &Platform::PyPI, + )), + CheckCategory::Pyflakes => { + Some(("https://pypi.org/project/pyflakes/2.5.0/", &Platform::PyPI)) } - CheckCategory::Pydocstyle => { - Some(("https://pypi.org/project/pydocstyle/6.1.1/", "PyPI")) + CheckCategory::Pylint => { + Some(("https://pypi.org/project/pylint/2.15.7/", &Platform::PyPI)) } - CheckCategory::Pyflakes => Some(("https://pypi.org/project/pyflakes/2.5.0/", "PyPI")), - CheckCategory::Pylint => Some(("https://pypi.org/project/pylint/2.15.7/", "PyPI")), - CheckCategory::PygrepHooks => { - Some(("https://github.com/pre-commit/pygrep-hooks", "GitHub")) + CheckCategory::PygrepHooks => Some(( + "https://github.com/pre-commit/pygrep-hooks", + &Platform::GitHub, + )), + CheckCategory::Pyupgrade => { + Some(("https://pypi.org/project/pyupgrade/3.2.0/", &Platform::PyPI)) } - CheckCategory::Pyupgrade => Some(("https://pypi.org/project/pyupgrade/3.2.0/", "PyPI")), CheckCategory::Ruff => None, } } @@ -682,6 +720,8 @@ pub enum CheckKind { BooleanPositionalArgInFunctionDefinition, BooleanDefaultValueInFunctionDefinition, BooleanPositionalValueInFunctionCall, + // pygrep-hooks + NoEval, // Ruff AmbiguousUnicodeCharacterString(char, char), AmbiguousUnicodeCharacterDocstring(char, char), @@ -689,8 +729,6 @@ pub enum CheckKind { ConvertExitToSysExit, // Meta UnusedNOQA(Option>), - // pygrep-hooks - NoEval, } impl CheckCode { @@ -1002,6 +1040,8 @@ impl CheckCode { CheckCode::FBT001 => CheckKind::BooleanPositionalArgInFunctionDefinition, CheckCode::FBT002 => CheckKind::BooleanDefaultValueInFunctionDefinition, CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall, + // pygrep-hooks + CheckCode::PGH001 => CheckKind::NoEval, // Ruff CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'), CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'), @@ -1009,8 +1049,6 @@ impl CheckCode { CheckCode::RUF101 => CheckKind::ConvertExitToSysExit, // Meta CheckCode::M001 => CheckKind::UnusedNOQA(None), - // pygrep-hooks - CheckCode::PGH001 => CheckKind::NoEval, } } @@ -1486,12 +1524,14 @@ impl CheckKind { CheckKind::HardcodedPasswordString(..) => &CheckCode::S105, CheckKind::HardcodedPasswordFuncArg(..) => &CheckCode::S106, CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107, - // McCabe + // mccabe CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901, // flake8-boolean-trap CheckKind::BooleanPositionalArgInFunctionDefinition => &CheckCode::FBT001, CheckKind::BooleanDefaultValueInFunctionDefinition => &CheckCode::FBT002, CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003, + // pygrep-hooks + CheckKind::NoEval => &CheckCode::PGH001, // Ruff CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001, CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002, @@ -1499,8 +1539,6 @@ impl CheckKind { CheckKind::ConvertExitToSysExit => &CheckCode::RUF101, // Meta CheckKind::UnusedNOQA(_) => &CheckCode::M001, - // pygrep-hooks - CheckKind::NoEval => &CheckCode::PGH001, } } @@ -2215,7 +2253,7 @@ impl CheckKind { } // flake8-blind-except CheckKind::BlindExcept => "Blind except Exception: statement".to_string(), - // McCabe + // mccabe CheckKind::FunctionIsTooComplex(name, complexity) => { format!("`{name}` is too complex ({complexity})") } @@ -2229,6 +2267,8 @@ impl CheckKind { CheckKind::BooleanPositionalValueInFunctionCall => { "Boolean positional value in function call".to_string() } + // pygrep-hooks + CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), // Ruff CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => { format!( @@ -2268,8 +2308,6 @@ impl CheckKind { format!("Unused `noqa` directive for: {codes}") } }, - // pygrep-hooks - CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), } }