From 88561c4d43b124bde0ee4c0bb7155cc1504b906e Mon Sep 17 00:00:00 2001 From: Steve C Date: Wed, 18 Oct 2023 01:54:25 -0400 Subject: [PATCH] implement C2401 --- .../test/fixtures/pylint/non_ascii_name.py | 9 ++++ .../src/checkers/ast/analyze/statement.rs | 11 +++++ crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/pylint/mod.rs | 1 + .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + .../src/rules/pylint/rules/non_ascii_name.rs | 41 +++++++++++++++++++ ...int__tests__PLC2401_non_ascii_name.py.snap | 30 ++++++++++++++ ruff.schema.json | 4 ++ 8 files changed, 99 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/non_ascii_name.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2401_non_ascii_name.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/non_ascii_name.py b/crates/ruff_linter/resources/test/fixtures/pylint/non_ascii_name.py new file mode 100644 index 00000000000000..6953587f0cca0e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/non_ascii_name.py @@ -0,0 +1,9 @@ +ápple_count: int = 1 # C2401 +ápple_count += 2 # C2401 +ápple_count = 3 # C2401 + +# this rule only works on assignment! +ápple_count == 3 # Ok + +# normal ascii +apple_count = 4 # Ok diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index bf5313b981e004..2fd7f3d86ff60c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1009,6 +1009,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => { + if checker.enabled(Rule::NonAsciiName) { + pylint::rules::non_ascii_name(checker, target); + } if checker.enabled(Rule::GlobalStatement) { if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { pylint::rules::global_statement(checker, id); @@ -1311,6 +1314,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => { + if checker.enabled(Rule::NonAsciiName) { + for target in targets { + pylint::rules::non_ascii_name(checker, target); + } + } if checker.enabled(Rule::LambdaAssignment) { if let [target] = &targets[..] { pycodestyle::rules::lambda_assignment(checker, target, value, None, stmt); @@ -1424,6 +1432,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { .. }, ) => { + if checker.enabled(Rule::NonAsciiName) { + pylint::rules::non_ascii_name(checker, target); + } if let Some(value) = value { if checker.enabled(Rule::LambdaAssignment) { pycodestyle::rules::lambda_assignment( diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 6835e7e725a98a..97b9073b1c8b01 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -211,6 +211,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0205") => (RuleGroup::Stable, rules::pylint::rules::SingleStringSlots), (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), + (Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName), #[allow(deprecated)] (Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString), (Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index d17a662cbdb4ff..1602c6b9cb6065 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -138,6 +138,7 @@ mod tests { #[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))] #[test_case(Rule::MisplacedBareRaise, Path::new("misplaced_bare_raise.py"))] #[test_case(Rule::LiteralMembership, Path::new("literal_membership.py"))] + #[test_case(Rule::NonAsciiName, Path::new("non_ascii_name.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index c4d7bd831b8ed0..390725f1ee4440 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -34,6 +34,7 @@ pub(crate) use named_expr_without_context::*; pub(crate) use nested_min_max::*; pub(crate) use no_self_use::*; pub(crate) use nonlocal_without_binding::*; +pub(crate) use non_ascii_name::*; pub(crate) use property_with_parameters::*; pub(crate) use redefined_loop_name::*; pub(crate) use repeated_equality_comparison::*; @@ -98,6 +99,7 @@ mod named_expr_without_context; mod nested_min_max; mod no_self_use; mod nonlocal_without_binding; +mod non_ascii_name; mod property_with_parameters; mod redefined_loop_name; mod repeated_equality_comparison; diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs new file mode 100644 index 00000000000000..dd8ea271d5f5c0 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs @@ -0,0 +1,41 @@ +use ruff_python_ast::{self as ast, Expr}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for the use of non-ASCII characters in symbol names. +/// +/// ## Why is this bad? +/// Pylint discourages the use of non-ASCII characters in symbol names as +/// they can cause confusion and compatibility issues. +/// +/// ## References +/// - [PEP 672](https://peps.python.org/pep-0672/) +#[violation] +pub struct NonAsciiName; + +impl Violation for NonAsciiName { + #[derive_message_formats] + fn message(&self) -> String { + format!("Symbol name contains a non-ASCII character, consider renaming it.") + } +} + +/// PLC2401 +pub(crate) fn non_ascii_name(checker: &mut Checker, target: &Expr) { + let Expr::Name(ast::ExprName { id, .. }) = target else { + return; + }; + + if id.is_ascii() { + return; + } + + checker + .diagnostics + .push(Diagnostic::new(NonAsciiName, target.range())); +} \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2401_non_ascii_name.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2401_non_ascii_name.py.snap new file mode 100644 index 00000000000000..bdc2358e1d7c65 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2401_non_ascii_name.py.snap @@ -0,0 +1,30 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +non_ascii_name.py:1:1: PLC2401 Symbol name contains a non-ASCII character, consider renaming it. + | +1 | ápple_count: int = 1 # C2401 + | ^^^^^^^^^^^ PLC2401 +2 | ápple_count += 2 # C2401 +3 | ápple_count = 3 # C2401 + | + +non_ascii_name.py:2:1: PLC2401 Symbol name contains a non-ASCII character, consider renaming it. + | +1 | ápple_count: int = 1 # C2401 +2 | ápple_count += 2 # C2401 + | ^^^^^^^^^^^ PLC2401 +3 | ápple_count = 3 # C2401 + | + +non_ascii_name.py:3:1: PLC2401 Symbol name contains a non-ASCII character, consider renaming it. + | +1 | ápple_count: int = 1 # C2401 +2 | ápple_count += 2 # C2401 +3 | ápple_count = 3 # C2401 + | ^^^^^^^^^^^ PLC2401 +4 | +5 | # this rule only works on assignment! + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 7d676b8f99ea24..dfcc0c72ec42c9 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2906,6 +2906,10 @@ "PLC19", "PLC190", "PLC1901", + "PLC2", + "PLC24", + "PLC240", + "PLC2401", "PLC3", "PLC30", "PLC300",