From e917d309f1d37887a9f5ea11698389caa6c76b11 Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Wed, 6 Aug 2025 02:42:51 -0400 Subject: [PATCH] [`flake8_import_conventions`] Avoid false positives for NFKC-normalized `__debug__` import aliases in ICN001 (#19411) Co-authored-by: Micha Reiser --- Cargo.lock | 1 + crates/ruff/tests/lint.rs | 31 ++++++++++++++++++++++ crates/ruff_workspace/Cargo.toml | 1 + crates/ruff_workspace/src/configuration.rs | 3 ++- crates/ruff_workspace/src/options.rs | 22 ++++++++++++--- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index deb4027e6e..4925f1a866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3389,6 +3389,7 @@ dependencies = [ "strum", "tempfile", "toml 0.9.4", + "unicode-normalization", ] [[package]] diff --git a/crates/ruff/tests/lint.rs b/crates/ruff/tests/lint.rs index ccb4ac0809..52a374477b 100644 --- a/crates/ruff/tests/lint.rs +++ b/crates/ruff/tests/lint.rs @@ -4996,6 +4996,37 @@ fn flake8_import_convention_invalid_aliases_config_module_name() -> Result<()> { Ok(()) } +#[test] +fn flake8_import_convention_nfkc_normalization() -> Result<()> { + let tempdir = TempDir::new()?; + let ruff_toml = tempdir.path().join("ruff.toml"); + fs::write( + &ruff_toml, + r#" +[lint.flake8-import-conventions.aliases] +"test.module" = "_﹏π˜₯𝘦𝘣𝘢𝘨﹏﹏" +"#, + )?; + + insta::with_settings!({ + filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .args(STDIN_BASE_OPTIONS) + .arg("--config") + .arg(&ruff_toml) + , @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + ruff failed + Cause: Invalid alias for module 'test.module': alias normalizes to '__debug__', which is not allowed. + ");}); + Ok(()) +} + #[test] fn flake8_import_convention_unused_aliased_import() { assert_cmd_snapshot!( diff --git a/crates/ruff_workspace/Cargo.toml b/crates/ruff_workspace/Cargo.toml index f1c8f09b83..7b075f0344 100644 --- a/crates/ruff_workspace/Cargo.toml +++ b/crates/ruff_workspace/Cargo.toml @@ -45,6 +45,7 @@ serde = { workspace = true } shellexpand = { workspace = true } strum = { workspace = true } toml = { workspace = true } +unicode-normalization = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] etcetera = { workspace = true } diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 2f8ef7b868..fef7148f64 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -250,7 +250,8 @@ impl Configuration { .unwrap_or_default(); let flake8_import_conventions = lint .flake8_import_conventions - .map(Flake8ImportConventionsOptions::into_settings) + .map(Flake8ImportConventionsOptions::try_into_settings) + .transpose()? .unwrap_or_default(); conflicting_import_settings(&isort, &flake8_import_conventions)?; diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 44716e5b12..5578f4e504 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; use strum::IntoEnumIterator; +use unicode_normalization::UnicodeNormalization; use crate::settings::LineEnding; use ruff_formatter::IndentStyle; @@ -1650,7 +1651,9 @@ impl<'de> Deserialize<'de> for Alias { } impl Flake8ImportConventionsOptions { - pub fn into_settings(self) -> flake8_import_conventions::settings::Settings { + pub fn try_into_settings( + self, + ) -> anyhow::Result { let mut aliases: FxHashMap = match self.aliases { Some(options_aliases) => options_aliases .into_iter() @@ -1666,11 +1669,22 @@ impl Flake8ImportConventionsOptions { ); } - flake8_import_conventions::settings::Settings { - aliases, + let mut normalized_aliases: FxHashMap = FxHashMap::default(); + for (module, alias) in aliases { + let normalized_alias = alias.nfkc().collect::(); + if normalized_alias == "__debug__" { + anyhow::bail!( + "Invalid alias for module '{module}': alias normalizes to '__debug__', which is not allowed." + ); + } + normalized_aliases.insert(module, normalized_alias); + } + + Ok(flake8_import_conventions::settings::Settings { + aliases: normalized_aliases, banned_aliases: self.banned_aliases.unwrap_or_default(), banned_from: self.banned_from.unwrap_or_default(), - } + }) } }