From 7647cafe1277519b757dd57069f6195612d34e98 Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Mon, 6 Feb 2023 22:49:18 -0500 Subject: [PATCH] [`pylint`]: bidirectional-unicode (#2589) --- README.md | 1 + .../fixtures/pylint/bidirectional_unicode.py | 24 ++++++++ crates/ruff/src/checkers/ast.rs | 3 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 1 + .../pylint/rules/bidirectional_unicode.rs | 49 +++++++++++++++++ crates/ruff/src/rules/pylint/rules/mod.rs | 2 + ...sts__PLE2402_bidirectional_unicode.py.snap | 55 +++++++++++++++++++ ...sts__PLE2502_bidirectional_unicode.py.snap | 55 +++++++++++++++++++ ruff.schema.json | 4 ++ 10 files changed, 195 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/pylint/bidirectional_unicode.py create mode 100644 crates/ruff/src/rules/pylint/rules/bidirectional_unicode.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2402_bidirectional_unicode.py.snap create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap diff --git a/README.md b/README.md index 55948e79fe..e1fbaf66a3 100644 --- a/README.md +++ b/README.md @@ -1338,6 +1338,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI. | PLE0605 | invalid-all-format | Invalid format for `__all__`, must be `tuple` or `list` | | | PLE1142 | await-outside-async | `await` should be used within an async function | | | PLE1310 | bad-str-strip-call | String `{strip}` call contains duplicate characters (did you mean `{removal}`?) | | +| PLE2502 | bidirectional-unicode | Avoid using bidirectional unicode | | #### Refactor (PLR) diff --git a/crates/ruff/resources/test/fixtures/pylint/bidirectional_unicode.py b/crates/ruff/resources/test/fixtures/pylint/bidirectional_unicode.py new file mode 100644 index 0000000000..dfa1e2e633 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/bidirectional_unicode.py @@ -0,0 +1,24 @@ +# E2502 +print("\u202B\u202E\u05e9\u05DC\u05D5\u05DD\u202C") + +# E2502 +print("שלום‬") + +# E2502 +example = "x‏" * 100 # "‏x" is assigned + +# E2502 +if access_level != "none‮⁦": # Check if admin ⁩⁦' and access_level != 'user + print("You are an admin.") + + +# E2502 +def subtract_funds(account: str, amount: int): + """Subtract funds from bank account then ⁧""" + return + bank[account] -= amount + return + + +# OK +print("Hello World") diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index b26d3a5abc..ba142835af 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -3107,6 +3107,9 @@ where if self.settings.rules.enabled(&Rule::RewriteUnicodeLiteral) { pyupgrade::rules::rewrite_unicode_literal(self, expr, kind.as_deref()); } + if self.settings.rules.enabled(&Rule::BidirectionalUnicode) { + pylint::rules::bidirectional_unicode(self, expr, value); + } } ExprKind::Lambda { args, body, .. } => { if self.settings.rules.enabled(&Rule::PreferListBuiltin) { diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 80dda6da50..adf8dafb61 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -107,6 +107,7 @@ ruff_macros::define_rule_mapping!( // pylint PLE0604 => rules::pylint::rules::InvalidAllObject, PLE0605 => rules::pylint::rules::InvalidAllFormat, + PLE2502 => rules::pylint::rules::BidirectionalUnicode, PLE1310 => rules::pylint::rules::BadStrStripCall, PLC0414 => rules::pylint::rules::UselessImportAlias, PLC3002 => rules::pylint::rules::UnnecessaryDirectLambdaCall, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 17fbb7b6bd..d587fde9d6 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -40,6 +40,7 @@ mod tests { #[test_case(Rule::TooManyArguments, Path::new("too_many_arguments.py"); "PLR0913")] #[test_case(Rule::TooManyBranches, Path::new("too_many_branches.py"); "PLR0912")] #[test_case(Rule::TooManyStatements, Path::new("too_many_statements.py"); "PLR0915")] + #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")] #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy()); diff --git a/crates/ruff/src/rules/pylint/rules/bidirectional_unicode.rs b/crates/ruff/src/rules/pylint/rules/bidirectional_unicode.rs new file mode 100644 index 0000000000..1281557d77 --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/bidirectional_unicode.rs @@ -0,0 +1,49 @@ +use rustpython_ast::Expr; + +use ruff_macros::derive_message_formats; + +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +const BIDI_UNICODE: [char; 10] = [ + '\u{202A}', //{LEFT-TO-RIGHT EMBEDDING} + '\u{202B}', //{RIGHT-TO-LEFT EMBEDDING} + '\u{202C}', //{POP DIRECTIONAL FORMATTING} + '\u{202D}', //{LEFT-TO-RIGHT OVERRIDE} + '\u{202E}', //{RIGHT-TO-LEFT OVERRIDE} + '\u{2066}', //{LEFT-TO-RIGHT ISOLATE} + '\u{2067}', //{RIGHT-TO-LEFT ISOLATE} + '\u{2068}', //{FIRST STRONG ISOLATE} + '\u{2069}', //{POP DIRECTIONAL ISOLATE} + // The following was part of PEP 672: + // https://www.python.org/dev/peps/pep-0672/ + // so the list above might not be complete + '\u{200F}', //{RIGHT-TO-LEFT MARK} + // We don't use + // "\u200E" # \n{LEFT-TO-RIGHT MARK} + // as this is the default for latin files and can't be used + // to hide code +]; + +define_violation!( + pub struct BidirectionalUnicode; +); +impl Violation for BidirectionalUnicode { + #[derive_message_formats] + fn message(&self) -> String { + format!("Avoid using bidirectional unicode") + } +} + +/// PLE2502 +pub fn bidirectional_unicode(checker: &mut Checker, expr: &Expr, value: &str) { + if value.contains(BIDI_UNICODE) { + checker.diagnostics.push(Diagnostic::new( + BidirectionalUnicode, + Range::from_located(expr), + )); + } +} diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index 3a9bc19369..53e6df8964 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -1,5 +1,6 @@ pub use await_outside_async::{await_outside_async, AwaitOutsideAsync}; pub use bad_str_strip_call::{bad_str_strip_call, BadStrStripCall}; +pub use bidirectional_unicode::{bidirectional_unicode, BidirectionalUnicode}; pub use comparison_of_constant::{comparison_of_constant, ComparisonOfConstant}; pub use consider_using_sys_exit::{consider_using_sys_exit, ConsiderUsingSysExit}; pub use global_variable_not_assigned::GlobalVariableNotAssigned; @@ -25,6 +26,7 @@ pub use useless_import_alias::{useless_import_alias, UselessImportAlias}; mod await_outside_async; mod bad_str_strip_call; +mod bidirectional_unicode; mod comparison_of_constant; mod consider_using_sys_exit; mod global_variable_not_assigned; diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2402_bidirectional_unicode.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2402_bidirectional_unicode.py.snap new file mode 100644 index 0000000000..8e100eefe2 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2402_bidirectional_unicode.py.snap @@ -0,0 +1,55 @@ +--- +source: src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + BidirectionalUnicode: ~ + location: + row: 2 + column: 6 + end_location: + row: 2 + column: 50 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 3 + column: 6 + end_location: + row: 3 + column: 13 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 5 + column: 10 + end_location: + row: 5 + column: 14 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 7 + column: 19 + end_location: + row: 7 + column: 27 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 11 + column: 4 + end_location: + row: 11 + column: 49 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap new file mode 100644 index 0000000000..d9f1fba9c7 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + BidirectionalUnicode: ~ + location: + row: 2 + column: 6 + end_location: + row: 2 + column: 50 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 5 + column: 6 + end_location: + row: 5 + column: 13 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 8 + column: 10 + end_location: + row: 8 + column: 14 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 11 + column: 19 + end_location: + row: 11 + column: 27 + fix: ~ + parent: ~ +- kind: + BidirectionalUnicode: ~ + location: + row: 17 + column: 4 + end_location: + row: 17 + column: 49 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index f2aa50ac58..ef681ac176 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1679,6 +1679,10 @@ "PLE13", "PLE131", "PLE1310", + "PLE2", + "PLE25", + "PLE250", + "PLE2502", "PLR", "PLR0", "PLR01",