diff --git a/crates/ruff/tests/integration_test.rs b/crates/ruff/tests/integration_test.rs index 109239da4d96a..b794495d1fd80 100644 --- a/crates/ruff/tests/integration_test.rs +++ b/crates/ruff/tests/integration_test.rs @@ -1228,8 +1228,8 @@ fn deprecated_multiple_direct() { Found 2 errors. ----- stderr ----- - warning: Rule `RUF921` is deprecated and will be removed in a future release. warning: Rule `RUF920` is deprecated and will be removed in a future release. + warning: Rule `RUF921` is deprecated and will be removed in a future release. "###); } @@ -1297,8 +1297,8 @@ fn deprecated_multiple_direct_preview_enabled() { ----- stderr ----- ruff failed Cause: Selection of deprecated rules is not allowed when preview is enabled. Remove selection of: - - RUF921 - RUF920 + - RUF921 "###); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 92b91e11e9a0b..4aca236ccc63e 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -922,6 +922,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "008") => (RuleGroup::Stable, rules::ruff::rules::MutableDataclassDefault), (Ruff, "009") => (RuleGroup::Stable, rules::ruff::rules::FunctionCallInDataclassDefaultArgument), (Ruff, "010") => (RuleGroup::Stable, rules::ruff::rules::ExplicitFStringTypeConversion), + (Ruff, "011") => (RuleGroup::Removed, rules::ruff::rules::RuffStaticKeyDictComprehension), (Ruff, "012") => (RuleGroup::Stable, rules::ruff::rules::MutableClassDefault), (Ruff, "013") => (RuleGroup::Stable, rules::ruff::rules::ImplicitOptional), (Ruff, "015") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement), diff --git a/crates/ruff_linter/src/rule_selector.rs b/crates/ruff_linter/src/rule_selector.rs index a6486e0b302f5..001b6a5f22a41 100644 --- a/crates/ruff_linter/src/rule_selector.rs +++ b/crates/ruff_linter/src/rule_selector.rs @@ -44,6 +44,20 @@ impl From for RuleSelector { } } +impl Ord for RuleSelector { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // TODO(zanieb): We ought to put "ALL" and "Linter" selectors + // above those that are rule specific but it's not critical for now + self.prefix_and_code().cmp(&other.prefix_and_code()) + } +} + +impl PartialOrd for RuleSelector { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl FromStr for RuleSelector { type Err = ParseError; diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 819ef82c82ec1..0122f2bd7b1e8 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -17,6 +17,7 @@ pub(crate) use parenthesize_logical_operators::*; pub(crate) use quadratic_list_summation::*; pub(crate) use sort_dunder_all::*; pub(crate) use sort_dunder_slots::*; +pub(crate) use static_key_dict_comprehension::*; #[cfg(feature = "test-rules")] pub(crate) use test_rules::*; pub(crate) use unnecessary_dict_comprehension_for_iterable::*; @@ -42,9 +43,11 @@ mod mutable_fromkeys_value; mod never_union; mod pairwise_over_zipped; mod parenthesize_logical_operators; +mod quadratic_list_summation; mod sequence_sorting; mod sort_dunder_all; mod sort_dunder_slots; +mod static_key_dict_comprehension; #[cfg(feature = "test-rules")] pub(crate) mod test_rules; mod unnecessary_dict_comprehension_for_iterable; @@ -58,5 +61,3 @@ pub(crate) enum Context { Docstring, Comment, } - -mod quadratic_list_summation; diff --git a/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs b/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs new file mode 100644 index 0000000000000..1d2236c6737e7 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/static_key_dict_comprehension.rs @@ -0,0 +1,37 @@ +use ruff_diagnostics::Violation; +use ruff_macros::{derive_message_formats, violation}; + +/// ## Removed +/// This rule was implemented in `flake8-bugbear` and has been remapped to [B035] +/// +/// ## What it does +/// Checks for dictionary comprehensions that use a static key, like a string +/// literal or a variable defined outside the comprehension. +/// +/// ## Why is this bad? +/// Using a static key (like a string literal) in a dictionary comprehension +/// is usually a mistake, as it will result in a dictionary with only one key, +/// despite the comprehension iterating over multiple values. +/// +/// ## Example +/// ```python +/// data = ["some", "Data"] +/// {"key": value.upper() for value in data} +/// ``` +/// +/// Use instead: +/// ```python +/// data = ["some", "Data"] +/// {value: value.upper() for value in data} +/// ``` +/// +/// [B035]: https://docs.astral.sh/ruff/rules/static-key-dict-comprehension/ +#[violation] +pub struct RuffStaticKeyDictComprehension; + +impl Violation for RuffStaticKeyDictComprehension { + #[derive_message_formats] + fn message(&self) -> String { + format!("Dictionary comprehension uses static key") + } +} diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index ea0abf4322fe8..0c21393509e9c 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -951,7 +951,7 @@ impl LintConfiguration { } } - let removed_selectors = removed_selectors.iter().collect::>(); + let removed_selectors = removed_selectors.iter().sorted().collect::>(); match removed_selectors.as_slice() { [] => (), [selection] => { @@ -974,7 +974,7 @@ impl LintConfiguration { } } - for (from, target) in redirects { + for (from, target) in redirects.iter().sorted_by_key(|item| item.0) { // TODO(martin): This belongs into the ruff crate. warn_user_once_by_id!( from, @@ -984,7 +984,10 @@ impl LintConfiguration { ); } - let deprecated_nursery_selectors = deprecated_nursery_selectors.iter().collect::>(); + let deprecated_nursery_selectors = deprecated_nursery_selectors + .iter() + .sorted() + .collect::>(); match deprecated_nursery_selectors.as_slice() { [] => (), [selection] => { @@ -1005,14 +1008,14 @@ impl LintConfiguration { } if preview.mode.is_disabled() { - for selection in deprecated_selectors { + for selection in deprecated_selectors.iter().sorted() { let (prefix, code) = selection.prefix_and_code(); warn_user!( "Rule `{prefix}{code}` is deprecated and will be removed in a future release.", ); } } else { - let deprecated_selectors = deprecated_selectors.iter().collect::>(); + let deprecated_selectors = deprecated_selectors.iter().sorted().collect::>(); match deprecated_selectors.as_slice() { [] => (), [selection] => { @@ -1033,7 +1036,7 @@ impl LintConfiguration { } } - for selection in ignored_preview_selectors { + for selection in ignored_preview_selectors.iter().sorted() { let (prefix, code) = selection.prefix_and_code(); warn_user!("Selection `{prefix}{code}` has no effect because preview is not enabled.",); }