Add no-eval rule from pygrep-hooks (#994)

This commit is contained in:
Jonathan Plasse 2022-12-02 18:59:06 +01:00 committed by GitHub
parent 1a24d78f67
commit 117fcb6936
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 267 additions and 47 deletions

23
LICENSE
View file

@ -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

View file

@ -77,18 +77,20 @@ of [Conda](https://docs.conda.io/en/latest/):
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)
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)
@ -726,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.
@ -902,6 +912,7 @@ natively, including:
- [`yesqa`](https://github.com/asottile/yesqa)
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`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:
@ -946,8 +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),
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 +1274,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"]`

View file

@ -0,0 +1,9 @@
from ast import literal_eval
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View file

@ -0,0 +1,11 @@
def eval(content: str) -> None:
pass
eval("3 + 4")
literal_eval({1: 2})
def fn() -> None:
eval("3 + 4")

View file

@ -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');

View file

@ -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;
@ -1700,6 +1700,11 @@ where
}
}
// pygrep-hooks
if self.settings.enabled.contains(&CheckCode::PGH001) {
pygrep_hooks::checks::no_eval(self, func);
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF101) {
rules::plugins::convert_exit_to_sys_exit(self, func);

View file

@ -278,6 +278,8 @@ pub enum CheckCode {
RUF101,
// Meta
M001,
// pygrep-hooks
PGH001,
}
#[derive(EnumIter, Debug, PartialEq, Eq)]
@ -302,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 {
@ -331,51 +348,97 @@ 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 Platform)> {
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::Eradicate => {
Some(("https://pypi.org/project/eradicate/2.1.0/", &Platform::PyPI))
}
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::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/",
&Platform::PyPI,
)),
CheckCategory::Flake8BooleanTrap => Some((
"https://pypi.org/project/flake8-boolean-trap/0.1.0/",
&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::Flake8Comprehensions => Some((
"https://pypi.org/project/flake8-comprehensions/3.10.1/",
&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::Flake8TidyImports => Some((
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
&Platform::PyPI,
)),
CheckCategory::Isort => {
Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI))
}
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
CheckCategory::McCabe => {
Some(("https://pypi.org/project/mccabe/0.7.0/", &Platform::PyPI))
}
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::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/",
&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::Pylint => {
Some(("https://pypi.org/project/pylint/2.15.7/", &Platform::PyPI))
}
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::Ruff => None,
}
}
@ -657,6 +720,8 @@ pub enum CheckKind {
BooleanPositionalArgInFunctionDefinition,
BooleanDefaultValueInFunctionDefinition,
BooleanPositionalValueInFunctionCall,
// pygrep-hooks
NoEval,
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
@ -975,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'),
@ -1169,6 +1236,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,
@ -1456,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,
@ -2183,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})")
}
@ -2197,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!(

View file

@ -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,

View file

@ -65,6 +65,7 @@ pub mod printer;
mod pycodestyle;
mod pydocstyle;
mod pyflakes;
mod pygrep_hooks;
mod pylint;
mod python;
mod pyupgrade;

View file

@ -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)));
}
}
}
}

30
src/pygrep_hooks/mod.rs Normal file
View file

@ -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(())
}
}

View file

@ -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: ~

View file

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