diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 73e875bf20..6611d27fa2 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -65,6 +65,7 @@ use crate::docstrings::extraction::ExtractionTarget; use crate::importer::{ImportRequest, Importer, ResolutionError}; use crate::noqa::NoqaMapping; use crate::package::PackageRoot; +use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled}; use crate::registry::Rule; use crate::rules::pyflakes::rules::{ LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction, @@ -618,7 +619,7 @@ impl SemanticSyntaxContext for Checker<'_> { | SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_) | SemanticSyntaxErrorKind::DuplicateParameter(_) | SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => { - if self.settings.preview.is_enabled() { + if is_semantic_errors_enabled(self.settings) { self.semantic_errors.borrow_mut().push(error); } } @@ -2827,7 +2828,7 @@ impl<'a> Checker<'a> { } } else { if self.enabled(Rule::UndefinedExport) { - if self.settings.preview.is_enabled() + if is_undefined_export_in_dunder_init_enabled(self.settings) || !self.path.ends_with("__init__.py") { self.diagnostics.get_mut().push( diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index f6d9c491c8..daf197c7d7 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -5,6 +5,7 @@ use ruff_python_ast::PythonVersion; use ruff_python_trivia::CommentRanges; use crate::package::PackageRoot; +use crate::preview::is_allow_nested_roots_enabled; use crate::registry::Rule; use crate::rules::flake8_builtins::rules::stdlib_module_shadowing; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; @@ -24,6 +25,7 @@ pub(crate) fn check_file_path( // flake8-no-pep420 if settings.rules.enabled(Rule::ImplicitNamespacePackage) { + let allow_nested_roots = is_allow_nested_roots_enabled(settings); if let Some(diagnostic) = implicit_namespace_package( path, package, @@ -31,7 +33,7 @@ pub(crate) fn check_file_path( comment_ranges, &settings.project_root, &settings.src, - settings.preview, + allow_nested_roots, ) { diagnostics.push(diagnostic); } diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index cb01ac686b..da7c4b1e6f 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -13,6 +13,7 @@ use crate::fix::edits::delete_comment; use crate::noqa::{ Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping, }; +use crate::preview::is_check_file_level_directives_enabled; use crate::registry::{AsRule, Rule, RuleSet}; use crate::rule_redirects::get_redirect_target; use crate::rules::pygrep_hooks; @@ -110,7 +111,7 @@ pub(crate) fn check_noqa( && !exemption.includes(Rule::UnusedNOQA) && !per_file_ignores.contains(Rule::UnusedNOQA) { - let directives: Vec<_> = if settings.preview.is_enabled() { + let directives: Vec<_> = if is_check_file_level_directives_enabled(settings) { noqa_directives .lines() .iter() diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index 5c024cb5f8..8cb57446bc 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -34,6 +34,7 @@ pub mod message; mod noqa; pub mod package; pub mod packaging; +pub mod preview; pub mod pyproject_toml; pub mod registry; mod renamer; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index d4d9daef16..7f03f72e1a 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -30,6 +30,7 @@ use crate::fix::{fix_file, FixResult}; use crate::message::Message; use crate::noqa::add_noqa; use crate::package::PackageRoot; +use crate::preview::is_unsupported_syntax_enabled; use crate::registry::{AsRule, Rule, RuleSet}; #[cfg(any(feature = "test-rules", test))] use crate::rules::ruff::rules::test_rules::{self, TestRule, TEST_RULES}; @@ -360,7 +361,7 @@ pub fn check_path( } } - let syntax_errors = if settings.preview.is_enabled() { + let syntax_errors = if is_unsupported_syntax_enabled(settings) { parsed.unsupported_syntax_errors() } else { &[] diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs new file mode 100644 index 0000000000..f64821f80d --- /dev/null +++ b/crates/ruff_linter/src/preview.rs @@ -0,0 +1,118 @@ +//! Helpers to test if a specific preview style is enabled or not. +//! +//! The motivation for these functions isn't to avoid code duplication but to ease promoting preview behavior +//! to stable. The challenge with directly checking the `preview` attribute of [`LinterSettings`] is that it is unclear +//! which specific feature this preview check is for. Having named functions simplifies the promotion: +//! Simply delete the function and let Rust tell you which checks you have to remove. + +use crate::settings::LinterSettings; + +// https://github.com/astral-sh/ruff/issues/17412 +// https://github.com/astral-sh/ruff/issues/11934 +pub(crate) const fn is_semantic_errors_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/16429 +pub(crate) const fn is_unsupported_syntax_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// Rule-specific behavior + +// https://github.com/astral-sh/ruff/pull/17136 +pub(crate) const fn is_shell_injection_only_trusted_input_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/15541 +pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/7501 +pub(crate) const fn is_bool_subtype_of_annotation_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/10759 +pub(crate) const fn is_comprehension_with_min_max_sum_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/12657 +pub(crate) const fn is_check_comprehensions_in_tuple_call_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/issues/15347 +pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/12676 +pub(crate) const fn is_fix_future_annotations_in_stub_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/11074 +pub(crate) const fn is_only_add_return_none_at_end_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/12796 +pub(crate) const fn is_simplify_ternary_to_binary_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/16719 +pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/13919 +pub(crate) const fn is_fix_manual_list_comprehension_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/11436 +// https://github.com/astral-sh/ruff/pull/11168 +pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/15313 +pub(crate) const fn is_defer_optional_to_up045_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/8473 +pub(crate) const fn is_unicode_to_unicode_confusables_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/17078 +pub(crate) const fn is_support_slices_in_literal_concatenation_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/11370 +pub(crate) const fn is_undefined_export_in_dunder_init_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/14236 +pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + +// https://github.com/astral-sh/ruff/pull/17061 +pub(crate) const fn is_check_file_level_directives_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs index 539b03ad5a..fef19910e5 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs @@ -1,5 +1,6 @@ //! Checks relating to shell injection. +use crate::preview::is_shell_injection_only_trusted_input_enabled; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::helpers::Truthiness; @@ -324,7 +325,9 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) { } // S603 _ => { - if !is_trusted_input(arg) || checker.settings.preview.is_disabled() { + if !is_trusted_input(arg) + || !is_shell_injection_only_trusted_input_enabled(checker.settings) + { if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) { checker.report_diagnostic(Diagnostic::new( SubprocessWithoutShellEqualsTrue, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index f47ecc06f4..dc1765f56e 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operato use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::preview::is_suspicious_function_reference_enabled; use crate::registry::AsRule; /// ## What it does @@ -936,7 +937,7 @@ pub(crate) fn suspicious_function_call(checker: &Checker, call: &ExprCall) { } pub(crate) fn suspicious_function_reference(checker: &Checker, func: &Expr) { - if checker.settings.preview.is_disabled() { + if !is_suspicious_function_reference_enabled(checker.settings) { return; } @@ -1210,7 +1211,7 @@ fn suspicious_function( /// S308 pub(crate) fn suspicious_function_decorator(checker: &Checker, decorator: &Decorator) { // In preview mode, references are handled collectively by `suspicious_function_reference` - if checker.settings.preview.is_disabled() { + if !is_suspicious_function_reference_enabled(checker.settings) { suspicious_function(checker, &decorator.expression, None, decorator.range); } } diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index d7de297983..e54f4119c6 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -8,6 +8,7 @@ use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; +use crate::preview::is_bool_subtype_of_annotation_enabled; use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def; /// ## What it does @@ -128,7 +129,7 @@ pub(crate) fn boolean_type_hint_positional_argument( let Some(annotation) = parameter.annotation() else { continue; }; - if checker.settings.preview.is_enabled() { + if is_bool_subtype_of_annotation_enabled(checker.settings) { if !match_annotation_to_complex_bool(annotation, checker.semantic()) { continue; } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs index cfa611e7de..97d6af93ee 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs @@ -6,6 +6,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::preview::is_comprehension_with_min_max_sum_enabled; use crate::rules::flake8_comprehensions::fixes; /// ## What it does @@ -125,7 +126,7 @@ pub(crate) fn unnecessary_comprehension_in_call( if !(matches!( builtin_function, SupportedBuiltins::Any | SupportedBuiltins::All - ) || (checker.settings.preview.is_enabled() + ) || (is_comprehension_with_min_max_sum_enabled(checker.settings) && matches!( builtin_function, SupportedBuiltins::Sum | SupportedBuiltins::Min | SupportedBuiltins::Max diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs index 50a44a31f5..c4eb96b832 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs @@ -6,6 +6,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::preview::is_check_comprehensions_in_tuple_call_enabled; use crate::rules::flake8_comprehensions::fixes; use super::helpers; @@ -100,7 +101,9 @@ pub(crate) fn unnecessary_literal_within_tuple_call( let argument_kind = match argument { Expr::Tuple(_) => TupleLiteralKind::Tuple, Expr::List(_) => TupleLiteralKind::List, - Expr::ListComp(_) if checker.settings.preview.is_enabled() => TupleLiteralKind::ListComp, + Expr::ListComp(_) if is_check_comprehensions_in_tuple_call_enabled(checker.settings) => { + TupleLiteralKind::ListComp + } _ => return, }; if !checker.semantic().has_builtin_binding("tuple") { diff --git a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs index 4e91d20582..eb1c30a41a 100644 --- a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs +++ b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs @@ -10,7 +10,6 @@ use ruff_text_size::{TextRange, TextSize}; use crate::comments::shebang::ShebangDirective; use crate::fs; use crate::package::PackageRoot; -use crate::settings::types::PreviewMode; use crate::Locator; /// ## What it does @@ -60,7 +59,7 @@ pub(crate) fn implicit_namespace_package( comment_ranges: &CommentRanges, project_root: &Path, src: &[PathBuf], - preview: PreviewMode, + allow_nested_roots: bool, ) -> Option { if package.is_none() // Ignore non-`.py` files, which don't require an `__init__.py`. @@ -93,7 +92,7 @@ pub(crate) fn implicit_namespace_package( )); } - if preview.is_enabled() { + if allow_nested_roots { if let Some(PackageRoot::Nested { path: root }) = package.as_ref() { if path.ends_with("__init__.py") { // Identify the intermediary package that's missing the `__init__.py` file. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs index 81066c0027..daa54b7d20 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs @@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::preview::is_bad_version_info_in_non_stub_enabled; use crate::registry::Rule; /// ## What it does @@ -140,7 +141,7 @@ pub(crate) fn bad_version_info_comparison(checker: &Checker, test: &Expr, has_el if matches!(op, CmpOp::Lt) { if checker.enabled(Rule::BadVersionInfoOrder) // See https://github.com/astral-sh/ruff/issues/15347 - && (checker.source_type.is_stub() || checker.settings.preview.is_enabled()) + && (checker.source_type.is_stub() || is_bad_version_info_in_non_stub_enabled(checker.settings)) { if has_else_clause { checker.report_diagnostic(Diagnostic::new(BadVersionInfoOrder, test.range())); diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs index 8949ff40ad..ed71e19440 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/future_annotations_in_stub.rs @@ -3,7 +3,7 @@ use ruff_python_ast::StmtImportFrom; use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use crate::{checkers::ast::Checker, fix}; +use crate::{checkers::ast::Checker, fix, preview::is_fix_future_annotations_in_stub_enabled}; /// ## What it does /// Checks for the presence of the `from __future__ import annotations` import @@ -55,7 +55,7 @@ pub(crate) fn from_future_import(checker: &Checker, target: &StmtImportFrom) { let mut diagnostic = Diagnostic::new(FutureAnnotationsInStub, *range); - if checker.settings.preview.is_enabled() { + if is_fix_future_annotations_in_stub_enabled(checker.settings) { let stmt = checker.semantic().current_statement(); diagnostic.try_set_fix(|| { diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index a28597949e..aa91da5e74 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -21,6 +21,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::edits; use crate::fix::edits::adjust_indentation; +use crate::preview::is_only_add_return_none_at_end_enabled; use crate::registry::{AsRule, Rule}; use crate::rules::flake8_return::helpers::end_of_last_statement; use crate::Locator; @@ -552,7 +553,7 @@ fn implicit_return(checker: &Checker, function_def: &ast::StmtFunctionDef, stmt: return; } - if checker.settings.preview.is_enabled() { + if is_only_add_return_none_at_end_enabled(checker.settings) { add_return_none(checker, stmt, function_def.range()); } else { for implicit_stmt in implicit_stmts { diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index d96d11df9d..9d9929e145 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::fits; +use crate::preview::is_simplify_ternary_to_binary_enabled; /// ## What it does /// Check for `if`-`else`-blocks that can be replaced with a ternary operator. @@ -180,39 +181,40 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &Checker, stmt_if: &ast:: // - If `test == not body_value` and preview enabled, replace with `target_var = body_value and else_value` // - If `not test == body_value` and preview enabled, replace with `target_var = body_value and else_value` // - Otherwise, replace with `target_var = body_value if test else else_value` - let (contents, assignment_kind) = - match (checker.settings.preview.is_enabled(), test, body_value) { - (true, test_node, body_node) - if ComparableExpr::from(test_node) == ComparableExpr::from(body_node) - && !contains_effect(test_node, |id| { - checker.semantic().has_builtin_binding(id) - }) => - { - let target_var = &body_target; - let binary = assignment_binary_or(target_var, body_value, else_value); - (checker.generator().stmt(&binary), AssignmentKind::Binary) - } - (true, test_node, body_node) - if (test_node.as_unary_op_expr().is_some_and(|op_expr| { - op_expr.op.is_not() - && ComparableExpr::from(&op_expr.operand) == ComparableExpr::from(body_node) - }) || body_node.as_unary_op_expr().is_some_and(|op_expr| { - op_expr.op.is_not() - && ComparableExpr::from(&op_expr.operand) == ComparableExpr::from(test_node) - })) && !contains_effect(test_node, |id| { - checker.semantic().has_builtin_binding(id) - }) => - { - let target_var = &body_target; - let binary = assignment_binary_and(target_var, body_value, else_value); - (checker.generator().stmt(&binary), AssignmentKind::Binary) - } - _ => { - let target_var = &body_target; - let ternary = assignment_ternary(target_var, body_value, test, else_value); - (checker.generator().stmt(&ternary), AssignmentKind::Ternary) - } - }; + let (contents, assignment_kind) = match ( + is_simplify_ternary_to_binary_enabled(checker.settings), + test, + body_value, + ) { + (true, test_node, body_node) + if ComparableExpr::from(test_node) == ComparableExpr::from(body_node) + && !contains_effect(test_node, |id| checker.semantic().has_builtin_binding(id)) => + { + let target_var = &body_target; + let binary = assignment_binary_or(target_var, body_value, else_value); + (checker.generator().stmt(&binary), AssignmentKind::Binary) + } + (true, test_node, body_node) + if (test_node.as_unary_op_expr().is_some_and(|op_expr| { + op_expr.op.is_not() + && ComparableExpr::from(&op_expr.operand) == ComparableExpr::from(body_node) + }) || body_node.as_unary_op_expr().is_some_and(|op_expr| { + op_expr.op.is_not() + && ComparableExpr::from(&op_expr.operand) == ComparableExpr::from(test_node) + })) && !contains_effect(test_node, |id| { + checker.semantic().has_builtin_binding(id) + }) => + { + let target_var = &body_target; + let binary = assignment_binary_and(target_var, body_value, else_value); + (checker.generator().stmt(&binary), AssignmentKind::Binary) + } + _ => { + let target_var = &body_target; + let ternary = assignment_ternary(target_var, body_value, test, else_value); + (checker.generator().stmt(&ternary), AssignmentKind::Ternary) + } + }; // Don't flag if the resulting expression would exceed the maximum line length. if !fits( diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs index c2b87b6efe..f3f6ad728c 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_dict_comprehension.rs @@ -8,6 +8,7 @@ use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::preview::is_fix_manual_dict_comprehension_enabled; use crate::rules::perflint::helpers::{comment_strings_in_range, statement_deletion_range}; /// ## What it does @@ -229,7 +230,7 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF return; } - if checker.settings.preview.is_enabled() { + if is_fix_manual_dict_comprehension_enabled(checker.settings) { let binding_stmt = binding.statement(checker.semantic()); let binding_value = binding_stmt.and_then(|binding_stmt| match binding_stmt { ast::Stmt::AnnAssign(assign) => assign.value.as_deref(), diff --git a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs index 9d3a5ac916..24e99ec8b9 100644 --- a/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs +++ b/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs @@ -1,6 +1,9 @@ use ruff_python_ast::{self as ast, Arguments, Expr}; -use crate::{checkers::ast::Checker, rules::perflint::helpers::statement_deletion_range}; +use crate::{ + checkers::ast::Checker, preview::is_fix_manual_list_comprehension_enabled, + rules::perflint::helpers::statement_deletion_range, +}; use anyhow::{anyhow, Result}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; @@ -335,7 +338,7 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF ); // TODO: once this fix is stabilized, change the rule to always fixable - if checker.settings.preview.is_enabled() { + if is_fix_manual_list_comprehension_enabled(checker.settings) { diagnostic.try_set_fix(|| { convert_to_list_extend( comprehension_type, diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index 1a9af61beb..91fcb878a2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -16,6 +16,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix; +use crate::preview::is_dunder_init_fix_unused_import_enabled; use crate::registry::Rule; use crate::rules::{isort, isort::ImportSection, isort::ImportType}; @@ -352,7 +353,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) { let in_init = checker.path().ends_with("__init__.py"); let fix_init = !checker.settings.ignore_init_module_imports; - let preview_mode = checker.settings.preview.is_enabled(); + let preview_mode = is_dunder_init_fix_unused_import_enabled(checker.settings); let dunder_all_exprs = find_dunder_all_exprs(checker.semantic()); // Generate a diagnostic for every import, but share fixes across all imports within the same diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 22d27551df..48b5737fc4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -11,7 +11,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; use crate::fix::edits::pad; -use crate::settings::types::PreviewMode; +use crate::preview::is_defer_optional_to_up045_enabled; /// ## What it does /// Check for type annotations that can be rewritten based on [PEP 604] syntax. @@ -150,15 +150,16 @@ pub(crate) fn non_pep604_annotation( match operator { Pep604Operator::Optional => { - let (rule, diagnostic_kind) = match checker.settings.preview { - PreviewMode::Disabled => ( - Rule::NonPEP604AnnotationUnion, - DiagnosticKind::from(NonPEP604AnnotationUnion), - ), - PreviewMode::Enabled => ( + let (rule, diagnostic_kind) = if is_defer_optional_to_up045_enabled(checker.settings) { + ( Rule::NonPEP604AnnotationOptional, DiagnosticKind::from(NonPEP604AnnotationOptional), - ), + ) + } else { + ( + Rule::NonPEP604AnnotationUnion, + DiagnosticKind::from(NonPEP604AnnotationUnion), + ) }; if !checker.enabled(rule) { diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index 3457a55d7e..2b8f8d3e23 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, StringLike}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::preview::is_unicode_to_unicode_confusables_enabled; use crate::registry::AsRule; use crate::rules::ruff::rules::confusables::confusable; use crate::rules::ruff::rules::Context; @@ -264,9 +265,9 @@ fn ambiguous_unicode_character( // Check if the boundary character is itself an ambiguous unicode character, in which // case, it's always included as a diagnostic. if !current_char.is_ascii() { - if let Some(representant) = confusable(current_char as u32) - .filter(|representant| settings.preview.is_enabled() || representant.is_ascii()) - { + if let Some(representant) = confusable(current_char as u32).filter(|representant| { + is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii() + }) { let candidate = Candidate::new( TextSize::try_from(relative_offset).unwrap() + range.start(), current_char, @@ -280,9 +281,9 @@ fn ambiguous_unicode_character( } else if current_char.is_ascii() { // The current word contains at least one ASCII character. word_flags |= WordFlags::ASCII; - } else if let Some(representant) = confusable(current_char as u32) - .filter(|representant| settings.preview.is_enabled() || representant.is_ascii()) - { + } else if let Some(representant) = confusable(current_char as u32).filter(|representant| { + is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii() + }) { // The current word contains an ambiguous unicode character. word_candidates.push(Candidate::new( TextSize::try_from(relative_offset).unwrap() + range.start(), diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index 60dcd09b34..7c9b426505 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -5,6 +5,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::preview::is_support_slices_in_literal_concatenation_enabled; /// ## What it does /// Checks for uses of the `+` operator to concatenate collections. @@ -197,7 +198,8 @@ pub(crate) fn collection_literal_concatenation(checker: &Checker, expr: &Expr) { return; } - let should_support_slices = checker.settings.preview.is_enabled(); + let should_support_slices = + is_support_slices_in_literal_concatenation_enabled(checker.settings); let Some((new_expr, type_)) = concatenate_expressions(expr, should_support_slices) else { return;