Skip to content

Commit

Permalink
[pylint] - implement non-ascii-name (C2401) (#8038)
Browse files Browse the repository at this point in the history
## Summary

Adds [`non-ascii-name` /
`C2401`](https://pylint.pycqa.org/en/latest/user_guide/messages/convention/non-ascii-name.html)

See #970

## Test Plan

`cargo test` and manually
  • Loading branch information
diceroll123 authored Oct 20, 2023
1 parent 7a5f988 commit 90ebea8
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ápple_count: int = 1 # C2401
ápple_count += 2 # C2401
ápple_count = 3 # C2401

(ápple_count for ápple_count in y)


def func(ápple_count):
global ápple_count
nonlocal ápple_count


def ápple_count():
pass


match ápple_count:
case ápple_count:
pass

ápple_count: int

try:
1/0
except ápple_count:
pass

# OK
print(ápple_count)
ápple_count == 3
apple_count = 4
6 changes: 6 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
if !checker.any_enabled(&[
Rule::InvalidAllFormat,
Rule::InvalidAllObject,
Rule::NonAsciiName,
Rule::UnaliasedCollectionsAbcSetImport,
Rule::UnconventionalImportAlias,
Rule::UnusedVariable,
Expand Down Expand Up @@ -49,6 +50,11 @@ pub(crate) fn bindings(checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::NonAsciiName) {
if let Some(diagnostic) = pylint::rules::non_ascii_name(binding, checker.locator) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UnconventionalImportAlias) {
if let Some(diagnostic) = flake8_import_conventions::rules::unconventional_import_alias(
checker,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
checker.enabled(Rule::NonAsciiName);
if checker.enabled(Rule::LambdaAssignment) {
if let [target] = &targets[..] {
pycodestyle::rules::lambda_assignment(checker, target, value, None, stmt);
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
(Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName),
#[allow(deprecated)]
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/pylint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ mod tests {
#[test_case(Rule::GlobalAtModuleLevel, Path::new("global_at_module_level.py"))]
#[test_case(Rule::UnnecessaryLambda, Path::new("unnecessary_lambda.py"))]
#[test_case(Rule::NonAsciiImportName, Path::new("non_ascii_module_import.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(
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/pylint/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(crate) use named_expr_without_context::*;
pub(crate) use nested_min_max::*;
pub(crate) use no_self_use::*;
pub(crate) use non_ascii_module_import::*;
pub(crate) use non_ascii_name::*;
pub(crate) use nonlocal_without_binding::*;
pub(crate) use property_with_parameters::*;
pub(crate) use redefined_loop_name::*;
Expand Down Expand Up @@ -102,6 +103,7 @@ mod named_expr_without_context;
mod nested_min_max;
mod no_self_use;
mod non_ascii_module_import;
mod non_ascii_name;
mod nonlocal_without_binding;
mod property_with_parameters;
mod redefined_loop_name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::checkers::ast::Checker;
/// Checks for the use of non-ASCII characters in import statements.
///
/// ## Why is this bad?
/// Pylint discourages the use of non-ASCII characters in symbol names as
/// they can cause confusion and compatibility issues.
/// The use of non-ASCII characters in import statements can cause confusion
/// and compatibility issues (see: [PEP 672]).
///
/// ## Example
/// ```python
Expand All @@ -28,8 +28,7 @@ use crate::checkers::ast::Checker;
/// import bár as bar
/// ```
///
/// ## References
/// - [PEP 672](https://peps.python.org/pep-0672/)
/// [PEP 672]: https://peps.python.org/pep-0672/
#[violation]
pub struct NonAsciiImportName {
name: String,
Expand Down
116 changes: 116 additions & 0 deletions crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::fmt;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::{Binding, BindingKind};
use ruff_source_file::Locator;
use ruff_text_size::Ranged;

/// ## What it does
/// Checks for the use of non-ASCII characters in variable names.
///
/// ## Why is this bad?
/// The use of non-ASCII characters in variable names can cause confusion
/// and compatibility issues (see: [PEP 672]).
///
/// ## Example
/// ```python
/// ápple_count: int
/// ```
///
/// Use instead:
/// ```python
/// apple_count: int
/// ```
///
/// [PEP 672]: https://peps.python.org/pep-0672/
#[violation]
pub struct NonAsciiName {
name: String,
kind: Kind,
}

impl Violation for NonAsciiName {
#[derive_message_formats]
fn message(&self) -> String {
let Self { name, kind } = self;
format!("{kind} name `{name}` contains a non-ASCII character, consider renaming it")
}
}

/// PLC2401
pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option<Diagnostic> {
let name = binding.name(locator);
if name.is_ascii() {
return None;
}

let kind = match binding.kind {
BindingKind::Annotation => Kind::Annotation,
BindingKind::Argument => Kind::Argument,
BindingKind::NamedExprAssignment => Kind::NamedExprAssignment,
BindingKind::UnpackedAssignment => Kind::UnpackedAssignment,
BindingKind::Assignment => Kind::Assignment,
BindingKind::TypeParam => Kind::TypeParam,
BindingKind::LoopVar => Kind::LoopVar,
BindingKind::Global => Kind::Global,
BindingKind::Nonlocal(_) => Kind::Nonlocal,
BindingKind::ClassDefinition(_) => Kind::ClassDefinition,
BindingKind::FunctionDefinition(_) => Kind::FunctionDefinition,
BindingKind::BoundException => Kind::BoundException,

BindingKind::Builtin
| BindingKind::Export(_)
| BindingKind::FutureImport
| BindingKind::Import(_)
| BindingKind::FromImport(_)
| BindingKind::SubmoduleImport(_)
| BindingKind::Deletion
| BindingKind::UnboundException(_) => {
return None;
}
};

Some(Diagnostic::new(
NonAsciiName {
name: name.to_string(),
kind,
},
binding.range(),
))
}

#[derive(Debug, PartialEq, Eq)]
enum Kind {
Annotation,
Argument,
NamedExprAssignment,
UnpackedAssignment,
Assignment,
TypeParam,
LoopVar,
Global,
Nonlocal,
ClassDefinition,
FunctionDefinition,
BoundException,
}

impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Kind::Annotation => f.write_str("Annotation"),
Kind::Argument => f.write_str("Argument"),
Kind::NamedExprAssignment => f.write_str("Variable"),
Kind::UnpackedAssignment => f.write_str("Variable"),
Kind::Assignment => f.write_str("Variable"),
Kind::TypeParam => f.write_str("Type parameter"),
Kind::LoopVar => f.write_str("Variable"),
Kind::Global => f.write_str("Global"),
Kind::Nonlocal => f.write_str("Nonlocal"),
Kind::ClassDefinition => f.write_str("Class"),
Kind::FunctionDefinition => f.write_str("Function"),
Kind::BoundException => f.write_str("Exception"),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
non_ascii_name.py:1:1: PLC2401 Variable name `ápple_count` 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 Variable name `ápple_count` 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 Variable name `ápple_count` 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 |pple_count for ápple_count in y)
|

non_ascii_name.py:5:18: PLC2401 Variable name `ápple_count` contains a non-ASCII character, consider renaming it
|
3 | ápple_count = 3 # C2401
4 |
5 |pple_count for ápple_count in y)
| ^^^^^^^^^^^ PLC2401
|

non_ascii_name.py:8:10: PLC2401 Argument name `ápple_count` contains a non-ASCII character, consider renaming it
|
8 | def funcpple_count):
| ^^^^^^^^^^^ PLC2401
9 | global ápple_count
10 | nonlocal ápple_count
|

non_ascii_name.py:9:12: PLC2401 Global name `ápple_count` contains a non-ASCII character, consider renaming it
|
8 | def funcpple_count):
9 | global ápple_count
| ^^^^^^^^^^^ PLC2401
10 | nonlocal ápple_count
|

non_ascii_name.py:13:5: PLC2401 Function name `ápple_count` contains a non-ASCII character, consider renaming it
|
13 | def ápple_count():
| ^^^^^^^^^^^ PLC2401
14 | pass
|

non_ascii_name.py:18:10: PLC2401 Variable name `ápple_count` contains a non-ASCII character, consider renaming it
|
17 | match ápple_count:
18 | case ápple_count:
| ^^^^^^^^^^^ PLC2401
19 | pass
|

non_ascii_name.py:21:1: PLC2401 Annotation name `ápple_count` contains a non-ASCII character, consider renaming it
|
19 | pass
20 |
21 | ápple_count: int
| ^^^^^^^^^^^ PLC2401
22 |
23 | try:
|


1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 90ebea8

Please sign in to comment.