diff --git a/LICENSE b/LICENSE index 41a40ef3cb..a4b8c442ea 100644 --- a/LICENSE +++ b/LICENSE @@ -242,6 +242,31 @@ are: SOFTWARE. """ +- flake8-debugger, licensed as follows: + """ + MIT License + + Copyright (c) 2016 Joseph Kahn + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ + - flake8-tidy-imports, licensed as follows: """ MIT License diff --git a/README.md b/README.md index c8d264d175..8ca8c114da 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu 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-tidy-imports (I25)](#flake8-tidy-imports) 1. [flake8-print (T)](#flake8-print) 1. [flake8-quotes (Q)](#flake8-quotes) @@ -538,6 +539,14 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens | C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 | | C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | | +### flake8-debugger + +For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| T100 | Debugger | Import for `...` found | | + ### flake8-boolean-trap For more, see [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/0.1.0/) on PyPI. @@ -825,6 +834,7 @@ including: - [`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/) @@ -856,6 +866,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`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/) diff --git a/flake8_to_ruff/src/plugin.rs b/flake8_to_ruff/src/plugin.rs index 4d96524fd4..7c0262c798 100644 --- a/flake8_to_ruff/src/plugin.rs +++ b/flake8_to_ruff/src/plugin.rs @@ -10,6 +10,7 @@ pub enum Plugin { Flake8Bugbear, Flake8Builtins, Flake8Comprehensions, + Flake8Debugger, Flake8Docstrings, Flake8TidyImports, Flake8Print, @@ -30,6 +31,7 @@ impl FromStr for Plugin { "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-print" => Ok(Plugin::Flake8Print), @@ -51,9 +53,10 @@ impl Plugin { Plugin::Flake8Bugbear => CheckCodePrefix::B, Plugin::Flake8Builtins => CheckCodePrefix::A, Plugin::Flake8Comprehensions => CheckCodePrefix::C4, + Plugin::Flake8Debugger => CheckCodePrefix::T1, Plugin::Flake8Docstrings => CheckCodePrefix::D, Plugin::Flake8TidyImports => CheckCodePrefix::I25, - Plugin::Flake8Print => CheckCodePrefix::T, + Plugin::Flake8Print => CheckCodePrefix::T2, Plugin::Flake8Quotes => CheckCodePrefix::Q, Plugin::Flake8Annotations => CheckCodePrefix::ANN, Plugin::Flake8BlindExcept => CheckCodePrefix::BLE, @@ -69,6 +72,7 @@ impl Plugin { Plugin::Flake8Bugbear => vec![CheckCodePrefix::B], Plugin::Flake8Builtins => vec![CheckCodePrefix::A], Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4], + Plugin::Flake8Debugger => vec![CheckCodePrefix::T1], Plugin::Flake8Docstrings => { // Use the user-provided docstring. for key in ["docstring-convention", "docstring_convention"] { @@ -86,7 +90,7 @@ impl Plugin { DocstringConvention::PEP8.select() } Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25], - Plugin::Flake8Print => vec![CheckCodePrefix::T], + Plugin::Flake8Print => vec![CheckCodePrefix::T2], Plugin::Flake8Quotes => vec![CheckCodePrefix::Q], Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN], Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE], @@ -364,6 +368,7 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet) -> Vec "flake8-builtins", CheckCategory::Flake8Bugbear => "flake8-bugbear", CheckCategory::Flake8Comprehensions => "flake8-comprehensions", + CheckCategory::Flake8Debugger => "flake8-debugger", CheckCategory::Flake8TidyImports => "flake8-tidy-imports", CheckCategory::Flake8Print => "flake8-print", CheckCategory::Flake8Quotes => "flake8-quotes", @@ -326,6 +331,9 @@ impl CheckCategory { 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::Flake8TidyImports => { Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/") } @@ -472,6 +480,8 @@ pub enum CheckKind { UnnecessarySubscriptReversal(String), UnnecessaryComprehension(String), UnnecessaryMap(String), + // flake8-debugger + Debugger(DebuggerUsingType), // flake8-tidy-imports BannedRelativeImport(Strictness), // flake8-print @@ -754,6 +764,8 @@ impl CheckCode { } CheckCode::C416 => CheckKind::UnnecessaryComprehension("(list|set)".to_string()), CheckCode::C417 => CheckKind::UnnecessaryMap("(list|set|dict)".to_string()), + // flake8-debugger + CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())), // flake8-tidy-imports CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All), // flake8-print @@ -1007,6 +1019,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, @@ -1234,6 +1247,8 @@ impl CheckKind { CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415, CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416, CheckKind::UnnecessaryMap(_) => &CheckCode::C417, + // flake8-debugger + CheckKind::Debugger(_) => &CheckCode::T100, // flake8-tidy-imports CheckKind::BannedRelativeImport(_) => &CheckCode::I252, // flake8-print @@ -1718,6 +1733,11 @@ impl CheckKind { format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)") } } + // flake8-debugger + CheckKind::Debugger(using_type) => match using_type { + DebuggerUsingType::Call(name) => format!("Trace found: `{name}` used"), + DebuggerUsingType::Import(name) => format!("Import for `{name}` found"), + }, // flake8-tidy-imports CheckKind::BannedRelativeImport(strictness) => match strictness { Strictness::Parents => { diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 22718629fb..bb547202cc 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -290,6 +290,9 @@ pub enum CheckCodePrefix { S106, S107, T, + T1, + T10, + T100, T2, T20, T201, @@ -1149,7 +1152,10 @@ impl CheckCodePrefix { CheckCodePrefix::S105 => vec![CheckCode::S105], CheckCodePrefix::S106 => vec![CheckCode::S106], CheckCodePrefix::S107 => vec![CheckCode::S107], - CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203], + CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203], + CheckCodePrefix::T1 => vec![CheckCode::T100], + CheckCodePrefix::T10 => vec![CheckCode::T100], + CheckCodePrefix::T100 => vec![CheckCode::T100], CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203], CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203], CheckCodePrefix::T201 => vec![CheckCode::T201], @@ -1554,6 +1560,9 @@ impl CheckCodePrefix { CheckCodePrefix::S106 => PrefixSpecificity::Explicit, CheckCodePrefix::S107 => PrefixSpecificity::Explicit, CheckCodePrefix::T => PrefixSpecificity::Category, + CheckCodePrefix::T1 => PrefixSpecificity::Hundreds, + CheckCodePrefix::T10 => PrefixSpecificity::Tens, + CheckCodePrefix::T100 => PrefixSpecificity::Explicit, CheckCodePrefix::T2 => PrefixSpecificity::Hundreds, CheckCodePrefix::T20 => PrefixSpecificity::Tens, CheckCodePrefix::T201 => PrefixSpecificity::Explicit, diff --git a/src/flake8_debugger/checks.rs b/src/flake8_debugger/checks.rs new file mode 100644 index 0000000000..107cbbf1ec --- /dev/null +++ b/src/flake8_debugger/checks.rs @@ -0,0 +1,64 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use rustpython_ast::{Expr, Stmt}; + +use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path}; +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; +use crate::flake8_debugger::types::DebuggerUsingType; + +const DEBUGGERS: &[(&str, &str)] = &[ + ("pdb", "set_trace"), + ("pudb", "set_trace"), + ("ipdb", "set_trace"), + ("ipdb", "sset_trace"), + ("IPython.terminal.embed", "InteractiveShellEmbed"), + ("IPython.frontend.terminal.embed", "InteractiveShellEmbed"), + ("celery.contrib.rdb", "set_trace"), + ("builtins", "breakpoint"), + ("", "breakpoint"), +]; + +/// Checks for the presence of a debugger call. +pub fn debugger_call( + expr: &Expr, + func: &Expr, + from_imports: &FxHashMap<&str, FxHashSet<&str>>, + import_aliases: &FxHashMap<&str, &str>, +) -> Option { + let call_path = dealias_call_path(collect_call_paths(func), import_aliases); + if DEBUGGERS + .iter() + .any(|(module, member)| match_call_path(&call_path, module, member, from_imports)) + { + Some(Check::new( + CheckKind::Debugger(DebuggerUsingType::Call(call_path.join("."))), + Range::from_located(expr), + )) + } else { + None + } +} + +/// Checks for the presence of a debugger import. +pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option { + if let Some(module) = module { + if let Some((module_name, member)) = DEBUGGERS + .iter() + .find(|(module_name, member)| module_name == &module && member == &name) + { + return Some(Check::new( + CheckKind::Debugger(DebuggerUsingType::Import(format!("{module_name}.{member}"))), + Range::from_located(stmt), + )); + } + } else if DEBUGGERS + .iter() + .any(|(module_name, ..)| module_name == &name) + { + return Some(Check::new( + CheckKind::Debugger(DebuggerUsingType::Import(name.to_string())), + Range::from_located(stmt), + )); + } + None +} diff --git a/src/flake8_debugger/mod.rs b/src/flake8_debugger/mod.rs new file mode 100644 index 0000000000..62a8881c0e --- /dev/null +++ b/src/flake8_debugger/mod.rs @@ -0,0 +1,2 @@ +pub mod checks; +pub mod types; diff --git a/src/flake8_debugger/types.rs b/src/flake8_debugger/types.rs new file mode 100644 index 0000000000..97c1ac4f0c --- /dev/null +++ b/src/flake8_debugger/types.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebuggerUsingType { + Call(String), + Import(String), +} diff --git a/src/lib.rs b/src/lib.rs index b34883e946..7b2ed66aa5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,7 @@ pub mod flake8_boolean_trap; pub mod flake8_bugbear; mod flake8_builtins; mod flake8_comprehensions; +mod flake8_debugger; mod flake8_print; pub mod flake8_quotes; pub mod flake8_tidy_imports; diff --git a/src/linter.rs b/src/linter.rs index cd2148e6e7..5e814ba9c4 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -569,6 +569,7 @@ mod tests { #[test_case(CheckCode::S105, Path::new("S105.py"); "S105")] #[test_case(CheckCode::S106, Path::new("S106.py"); "S106")] #[test_case(CheckCode::S107, Path::new("S107.py"); "S107")] + #[test_case(CheckCode::T100, Path::new("T100.py"); "T100")] #[test_case(CheckCode::T201, Path::new("T201.py"); "T201")] #[test_case(CheckCode::T203, Path::new("T203.py"); "T203")] #[test_case(CheckCode::U001, Path::new("U001.py"); "U001")] diff --git a/src/snapshots/ruff__linter__tests__T100_T100.py.snap b/src/snapshots/ruff__linter__tests__T100_T100.py.snap new file mode 100644 index 0000000000..df82b6ca3d --- /dev/null +++ b/src/snapshots/ruff__linter__tests__T100_T100.py.snap @@ -0,0 +1,95 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + Debugger: + Call: breakpoint + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 12 + fix: ~ +- kind: + Debugger: + Import: pdb + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 10 + fix: ~ +- kind: + Debugger: + Import: builtins.breakpoint + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 31 + fix: ~ +- kind: + Debugger: + Import: pdb.set_trace + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 31 + fix: ~ +- kind: + Debugger: + Import: celery.contrib.rdb.set_trace + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 40 + fix: ~ +- kind: + Debugger: + Import: celery.contrib.rdb + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 25 + fix: ~ +- kind: + Debugger: + Call: breakpoint + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 12 + fix: ~ +- kind: + Debugger: + Call: set_trace + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 4 + fix: ~ +- kind: + Debugger: + Call: set_trace + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 11 + fix: ~ +