From b6522cb5342d80784f3585729f18d1b3470bd264 Mon Sep 17 00:00:00 2001 From: Kot Date: Thu, 28 Aug 2025 13:35:48 -0700 Subject: [PATCH] [`pylint`] Add U+061C to `PLE2502` (#20106) Resolves #20058 --- .../fixtures/pylint/bidirectional_unicode.py | 3 ++ crates/ruff_linter/src/preview.rs | 5 ++ crates/ruff_linter/src/rules/pylint/mod.rs | 24 +++++++++ .../pylint/rules/bidirectional_unicode.rs | 11 +++- ...sts__PLE2502_bidirectional_unicode.py.snap | 26 +++++----- ...iew__PLE2502_bidirectional_unicode.py.snap | 52 +++++++++++++++++++ 6 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE2502_bidirectional_unicode.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/bidirectional_unicode.py b/crates/ruff_linter/resources/test/fixtures/pylint/bidirectional_unicode.py index 25accb3aad..f72695d0c4 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/bidirectional_unicode.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/bidirectional_unicode.py @@ -4,6 +4,9 @@ print("שלום‬") # E2502 example = "x‏" * 100 # "‏x" is assigned +# E2502 +another = "x؜" * 50 # "؜x" is assigned + # E2502 if access_level != "none‮⁦": # Check if admin ⁩⁦' and access_level != 'user print("You are an admin.") diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 6b6a4d15e5..f932b2b059 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -255,3 +255,8 @@ pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSetti pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } + +// https://github.com/astral-sh/ruff/pull/20106 +pub(crate) const fn is_bidi_forbid_arabic_letter_mark_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 101c92a16b..e181c0a146 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -252,6 +252,30 @@ mod tests { Ok(()) } + #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "preview__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("pylint").join(path).as_path(), + &LinterSettings { + pylint: pylint::settings::Settings { + allow_dunder_method_names: FxHashSet::from_iter([ + "__special_custom_magic__".to_string() + ]), + ..pylint::settings::Settings::default() + }, + preview: PreviewMode::Enabled, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_diagnostics!(snapshot, diagnostics); + Ok(()) + } + #[test] fn continue_in_finally() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs index fce3e6b36d..0a80f28b45 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bidirectional_unicode.rs @@ -1,7 +1,9 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_source_file::Line; -use crate::{Violation, checkers::ast::LintContext}; +use crate::{ + Violation, checkers::ast::LintContext, preview::is_bidi_forbid_arabic_letter_mark_enabled, +}; const BIDI_UNICODE: [char; 10] = [ '\u{202A}', //{LEFT-TO-RIGHT EMBEDDING} @@ -60,7 +62,12 @@ impl Violation for BidirectionalUnicode { /// PLE2502 pub(crate) fn bidirectional_unicode(line: &Line, context: &LintContext) { - if line.contains(BIDI_UNICODE) { + if line.contains(BIDI_UNICODE) + || (is_bidi_forbid_arabic_letter_mark_enabled(context.settings()) + && line.contains( + '\u{061C}', //{ARABIC LETTER MARK} + )) + { context.report_diagnostic(BidirectionalUnicode, line.full_range()); } } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap index 18356831d7..0d584e8108 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2502_bidirectional_unicode.py.snap @@ -22,21 +22,21 @@ PLE2502 Contains control characters that can permit obfuscated code | PLE2502 Contains control characters that can permit obfuscated code - --> bidirectional_unicode.py:8:1 - | -7 | # E2502 -8 | if access_level != "none": # Check if admin ' and access_level != 'user - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -9 | print("You are an admin.") - | + --> bidirectional_unicode.py:11:1 + | +10 | # E2502 +11 | if access_level != "none": # Check if admin ' and access_level != 'user + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +12 | print("You are an admin.") + | PLE2502 Contains control characters that can permit obfuscated code - --> bidirectional_unicode.py:14:1 + --> bidirectional_unicode.py:17:1 | -12 | # E2502 -13 | def subtract_funds(account: str, amount: int): -14 | """Subtract funds from bank account then """ +15 | # E2502 +16 | def subtract_funds(account: str, amount: int): +17 | """Subtract funds from bank account then """ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -15 | return -16 | bank[account] -= amount +18 | return +19 | bank[account] -= amount | diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE2502_bidirectional_unicode.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE2502_bidirectional_unicode.py.snap new file mode 100644 index 0000000000..4a1555ebe8 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__preview__PLE2502_bidirectional_unicode.py.snap @@ -0,0 +1,52 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +PLE2502 Contains control characters that can permit obfuscated code + --> bidirectional_unicode.py:2:1 + | +1 | # E2502 +2 | print("שלום") + | ^^^^^^^^^^^^^ +3 | +4 | # E2502 + | + +PLE2502 Contains control characters that can permit obfuscated code + --> bidirectional_unicode.py:5:1 + | +4 | # E2502 +5 | example = "x‏" * 100 # "‏x" is assigned + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +6 | +7 | # E2502 + | + +PLE2502 Contains control characters that can permit obfuscated code + --> bidirectional_unicode.py:8:1 + | + 7 | # E2502 + 8 | another = "x؜" * 50 # "؜x" is assigned + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 | +10 | # E2502 + | + +PLE2502 Contains control characters that can permit obfuscated code + --> bidirectional_unicode.py:11:1 + | +10 | # E2502 +11 | if access_level != "none": # Check if admin ' and access_level != 'user + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +12 | print("You are an admin.") + | + +PLE2502 Contains control characters that can permit obfuscated code + --> bidirectional_unicode.py:17:1 + | +15 | # E2502 +16 | def subtract_funds(account: str, amount: int): +17 | """Subtract funds from bank account then """ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +18 | return +19 | bank[account] -= amount + |