Collect preview lint behaviors in separate module (#17646)

This PR collects all behavior gated under preview into a new module
`ruff_linter::preview` that exposes functions like
`is_my_new_feature_enabled` - just as is done in the formatter crate.
This commit is contained in:
Dylan 2025-04-28 09:12:24 -05:00 committed by GitHub
parent 1ad5015e19
commit 152a0b6585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 215 additions and 70 deletions

View File

@ -65,6 +65,7 @@ use crate::docstrings::extraction::ExtractionTarget;
use crate::importer::{ImportRequest, Importer, ResolutionError}; use crate::importer::{ImportRequest, Importer, ResolutionError};
use crate::noqa::NoqaMapping; use crate::noqa::NoqaMapping;
use crate::package::PackageRoot; use crate::package::PackageRoot;
use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled};
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::pyflakes::rules::{ use crate::rules::pyflakes::rules::{
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction, LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
@ -618,7 +619,7 @@ impl SemanticSyntaxContext for Checker<'_> {
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_) | SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
| SemanticSyntaxErrorKind::DuplicateParameter(_) | SemanticSyntaxErrorKind::DuplicateParameter(_)
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => { | SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
if self.settings.preview.is_enabled() { if is_semantic_errors_enabled(self.settings) {
self.semantic_errors.borrow_mut().push(error); self.semantic_errors.borrow_mut().push(error);
} }
} }
@ -2827,7 +2828,7 @@ impl<'a> Checker<'a> {
} }
} else { } else {
if self.enabled(Rule::UndefinedExport) { 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.path.ends_with("__init__.py")
{ {
self.diagnostics.get_mut().push( self.diagnostics.get_mut().push(

View File

@ -5,6 +5,7 @@ use ruff_python_ast::PythonVersion;
use ruff_python_trivia::CommentRanges; use ruff_python_trivia::CommentRanges;
use crate::package::PackageRoot; use crate::package::PackageRoot;
use crate::preview::is_allow_nested_roots_enabled;
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::flake8_builtins::rules::stdlib_module_shadowing; use crate::rules::flake8_builtins::rules::stdlib_module_shadowing;
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
@ -24,6 +25,7 @@ pub(crate) fn check_file_path(
// flake8-no-pep420 // flake8-no-pep420
if settings.rules.enabled(Rule::ImplicitNamespacePackage) { if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
let allow_nested_roots = is_allow_nested_roots_enabled(settings);
if let Some(diagnostic) = implicit_namespace_package( if let Some(diagnostic) = implicit_namespace_package(
path, path,
package, package,
@ -31,7 +33,7 @@ pub(crate) fn check_file_path(
comment_ranges, comment_ranges,
&settings.project_root, &settings.project_root,
&settings.src, &settings.src,
settings.preview, allow_nested_roots,
) { ) {
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }

View File

@ -13,6 +13,7 @@ use crate::fix::edits::delete_comment;
use crate::noqa::{ use crate::noqa::{
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping, Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
}; };
use crate::preview::is_check_file_level_directives_enabled;
use crate::registry::{AsRule, Rule, RuleSet}; use crate::registry::{AsRule, Rule, RuleSet};
use crate::rule_redirects::get_redirect_target; use crate::rule_redirects::get_redirect_target;
use crate::rules::pygrep_hooks; use crate::rules::pygrep_hooks;
@ -110,7 +111,7 @@ pub(crate) fn check_noqa(
&& !exemption.includes(Rule::UnusedNOQA) && !exemption.includes(Rule::UnusedNOQA)
&& !per_file_ignores.contains(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 noqa_directives
.lines() .lines()
.iter() .iter()

View File

@ -34,6 +34,7 @@ pub mod message;
mod noqa; mod noqa;
pub mod package; pub mod package;
pub mod packaging; pub mod packaging;
pub mod preview;
pub mod pyproject_toml; pub mod pyproject_toml;
pub mod registry; pub mod registry;
mod renamer; mod renamer;

View File

@ -30,6 +30,7 @@ use crate::fix::{fix_file, FixResult};
use crate::message::Message; use crate::message::Message;
use crate::noqa::add_noqa; use crate::noqa::add_noqa;
use crate::package::PackageRoot; use crate::package::PackageRoot;
use crate::preview::is_unsupported_syntax_enabled;
use crate::registry::{AsRule, Rule, RuleSet}; use crate::registry::{AsRule, Rule, RuleSet};
#[cfg(any(feature = "test-rules", test))] #[cfg(any(feature = "test-rules", test))]
use crate::rules::ruff::rules::test_rules::{self, TestRule, TEST_RULES}; 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() parsed.unsupported_syntax_errors()
} else { } else {
&[] &[]

View File

@ -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()
}

View File

@ -1,5 +1,6 @@
//! Checks relating to shell injection. //! Checks relating to shell injection.
use crate::preview::is_shell_injection_only_trusted_input_enabled;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::Truthiness; use ruff_python_ast::helpers::Truthiness;
@ -324,7 +325,9 @@ pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
} }
// S603 // 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) { if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.report_diagnostic(Diagnostic::new( checker.report_diagnostic(Diagnostic::new(
SubprocessWithoutShellEqualsTrue, SubprocessWithoutShellEqualsTrue,

View File

@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, Arguments, Decorator, Expr, ExprCall, Operato
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::is_suspicious_function_reference_enabled;
use crate::registry::AsRule; use crate::registry::AsRule;
/// ## What it does /// ## 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) { 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; return;
} }
@ -1210,7 +1211,7 @@ fn suspicious_function(
/// S308 /// S308
pub(crate) fn suspicious_function_decorator(checker: &Checker, decorator: &Decorator) { pub(crate) fn suspicious_function_decorator(checker: &Checker, decorator: &Decorator) {
// In preview mode, references are handled collectively by `suspicious_function_reference` // 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); suspicious_function(checker, &decorator.expression, None, decorator.range);
} }
} }

View File

@ -8,6 +8,7 @@ use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker; 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; use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// ## What it does /// ## What it does
@ -128,7 +129,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
let Some(annotation) = parameter.annotation() else { let Some(annotation) = parameter.annotation() else {
continue; 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()) { if !match_annotation_to_complex_bool(annotation, checker.semantic()) {
continue; continue;
} }

View File

@ -6,6 +6,7 @@ use ruff_python_ast::{self as ast, Expr, Keyword};
use ruff_text_size::{Ranged, TextSize}; use ruff_text_size::{Ranged, TextSize};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::is_comprehension_with_min_max_sum_enabled;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
/// ## What it does /// ## What it does
@ -125,7 +126,7 @@ pub(crate) fn unnecessary_comprehension_in_call(
if !(matches!( if !(matches!(
builtin_function, builtin_function,
SupportedBuiltins::Any | SupportedBuiltins::All SupportedBuiltins::Any | SupportedBuiltins::All
) || (checker.settings.preview.is_enabled() ) || (is_comprehension_with_min_max_sum_enabled(checker.settings)
&& matches!( && matches!(
builtin_function, builtin_function,
SupportedBuiltins::Sum | SupportedBuiltins::Min | SupportedBuiltins::Max SupportedBuiltins::Sum | SupportedBuiltins::Min | SupportedBuiltins::Max

View File

@ -6,6 +6,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::is_check_comprehensions_in_tuple_call_enabled;
use crate::rules::flake8_comprehensions::fixes; use crate::rules::flake8_comprehensions::fixes;
use super::helpers; use super::helpers;
@ -100,7 +101,9 @@ pub(crate) fn unnecessary_literal_within_tuple_call(
let argument_kind = match argument { let argument_kind = match argument {
Expr::Tuple(_) => TupleLiteralKind::Tuple, Expr::Tuple(_) => TupleLiteralKind::Tuple,
Expr::List(_) => TupleLiteralKind::List, 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, _ => return,
}; };
if !checker.semantic().has_builtin_binding("tuple") { if !checker.semantic().has_builtin_binding("tuple") {

View File

@ -10,7 +10,6 @@ use ruff_text_size::{TextRange, TextSize};
use crate::comments::shebang::ShebangDirective; use crate::comments::shebang::ShebangDirective;
use crate::fs; use crate::fs;
use crate::package::PackageRoot; use crate::package::PackageRoot;
use crate::settings::types::PreviewMode;
use crate::Locator; use crate::Locator;
/// ## What it does /// ## What it does
@ -60,7 +59,7 @@ pub(crate) fn implicit_namespace_package(
comment_ranges: &CommentRanges, comment_ranges: &CommentRanges,
project_root: &Path, project_root: &Path,
src: &[PathBuf], src: &[PathBuf],
preview: PreviewMode, allow_nested_roots: bool,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if package.is_none() if package.is_none()
// Ignore non-`.py` files, which don't require an `__init__.py`. // 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 let Some(PackageRoot::Nested { path: root }) = package.as_ref() {
if path.ends_with("__init__.py") { if path.ends_with("__init__.py") {
// Identify the intermediary package that's missing the `__init__.py` file. // Identify the intermediary package that's missing the `__init__.py` file.

View File

@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::is_bad_version_info_in_non_stub_enabled;
use crate::registry::Rule; use crate::registry::Rule;
/// ## What it does /// ## 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 matches!(op, CmpOp::Lt) {
if checker.enabled(Rule::BadVersionInfoOrder) if checker.enabled(Rule::BadVersionInfoOrder)
// See https://github.com/astral-sh/ruff/issues/15347 // 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 { if has_else_clause {
checker.report_diagnostic(Diagnostic::new(BadVersionInfoOrder, test.range())); checker.report_diagnostic(Diagnostic::new(BadVersionInfoOrder, test.range()));

View File

@ -3,7 +3,7 @@ use ruff_python_ast::StmtImportFrom;
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation}; use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata}; 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 /// ## What it does
/// Checks for the presence of the `from __future__ import annotations` import /// 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); 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(); let stmt = checker.semantic().current_statement();
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {

View File

@ -21,6 +21,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits; use crate::fix::edits;
use crate::fix::edits::adjust_indentation; use crate::fix::edits::adjust_indentation;
use crate::preview::is_only_add_return_none_at_end_enabled;
use crate::registry::{AsRule, Rule}; use crate::registry::{AsRule, Rule};
use crate::rules::flake8_return::helpers::end_of_last_statement; use crate::rules::flake8_return::helpers::end_of_last_statement;
use crate::Locator; use crate::Locator;
@ -552,7 +553,7 @@ fn implicit_return(checker: &Checker, function_def: &ast::StmtFunctionDef, stmt:
return; 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()); add_return_none(checker, stmt, function_def.range());
} else { } else {
for implicit_stmt in implicit_stmts { for implicit_stmt in implicit_stmts {

View File

@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::edits::fits; use crate::fix::edits::fits;
use crate::preview::is_simplify_ternary_to_binary_enabled;
/// ## What it does /// ## What it does
/// Check for `if`-`else`-blocks that can be replaced with a ternary operator. /// Check for `if`-`else`-blocks that can be replaced with a ternary operator.
@ -180,13 +181,14 @@ 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 `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` // - 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` // - Otherwise, replace with `target_var = body_value if test else else_value`
let (contents, assignment_kind) = let (contents, assignment_kind) = match (
match (checker.settings.preview.is_enabled(), test, body_value) { is_simplify_ternary_to_binary_enabled(checker.settings),
test,
body_value,
) {
(true, test_node, body_node) (true, test_node, body_node)
if ComparableExpr::from(test_node) == ComparableExpr::from(body_node) if ComparableExpr::from(test_node) == ComparableExpr::from(body_node)
&& !contains_effect(test_node, |id| { && !contains_effect(test_node, |id| checker.semantic().has_builtin_binding(id)) =>
checker.semantic().has_builtin_binding(id)
}) =>
{ {
let target_var = &body_target; let target_var = &body_target;
let binary = assignment_binary_or(target_var, body_value, else_value); let binary = assignment_binary_or(target_var, body_value, else_value);

View File

@ -8,6 +8,7 @@ use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; 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}; use crate::rules::perflint::helpers::{comment_strings_in_range, statement_deletion_range};
/// ## What it does /// ## What it does
@ -229,7 +230,7 @@ pub(crate) fn manual_dict_comprehension(checker: &Checker, for_stmt: &ast::StmtF
return; 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_stmt = binding.statement(checker.semantic());
let binding_value = binding_stmt.and_then(|binding_stmt| match binding_stmt { let binding_value = binding_stmt.and_then(|binding_stmt| match binding_stmt {
ast::Stmt::AnnAssign(assign) => assign.value.as_deref(), ast::Stmt::AnnAssign(assign) => assign.value.as_deref(),

View File

@ -1,6 +1,9 @@
use ruff_python_ast::{self as ast, Arguments, Expr}; 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 anyhow::{anyhow, Result};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; 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 // 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(|| { diagnostic.try_set_fix(|| {
convert_to_list_extend( convert_to_list_extend(
comprehension_type, comprehension_type,

View File

@ -16,6 +16,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix; use crate::fix;
use crate::preview::is_dunder_init_fix_unused_import_enabled;
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::{isort, isort::ImportSection, isort::ImportType}; 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 in_init = checker.path().ends_with("__init__.py");
let fix_init = !checker.settings.ignore_init_module_imports; 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()); 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 // Generate a diagnostic for every import, but share fixes across all imports within the same

View File

@ -11,7 +11,7 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::codes::Rule; use crate::codes::Rule;
use crate::fix::edits::pad; use crate::fix::edits::pad;
use crate::settings::types::PreviewMode; use crate::preview::is_defer_optional_to_up045_enabled;
/// ## What it does /// ## What it does
/// Check for type annotations that can be rewritten based on [PEP 604] syntax. /// 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 { match operator {
Pep604Operator::Optional => { Pep604Operator::Optional => {
let (rule, diagnostic_kind) = match checker.settings.preview { let (rule, diagnostic_kind) = if is_defer_optional_to_up045_enabled(checker.settings) {
PreviewMode::Disabled => ( (
Rule::NonPEP604AnnotationUnion,
DiagnosticKind::from(NonPEP604AnnotationUnion),
),
PreviewMode::Enabled => (
Rule::NonPEP604AnnotationOptional, Rule::NonPEP604AnnotationOptional,
DiagnosticKind::from(NonPEP604AnnotationOptional), DiagnosticKind::from(NonPEP604AnnotationOptional),
), )
} else {
(
Rule::NonPEP604AnnotationUnion,
DiagnosticKind::from(NonPEP604AnnotationUnion),
)
}; };
if !checker.enabled(rule) { if !checker.enabled(rule) {

View File

@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, StringLike};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::is_unicode_to_unicode_confusables_enabled;
use crate::registry::AsRule; use crate::registry::AsRule;
use crate::rules::ruff::rules::confusables::confusable; use crate::rules::ruff::rules::confusables::confusable;
use crate::rules::ruff::rules::Context; 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 // Check if the boundary character is itself an ambiguous unicode character, in which
// case, it's always included as a diagnostic. // case, it's always included as a diagnostic.
if !current_char.is_ascii() { if !current_char.is_ascii() {
if let Some(representant) = confusable(current_char as u32) if let Some(representant) = confusable(current_char as u32).filter(|representant| {
.filter(|representant| settings.preview.is_enabled() || representant.is_ascii()) is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
{ }) {
let candidate = Candidate::new( let candidate = Candidate::new(
TextSize::try_from(relative_offset).unwrap() + range.start(), TextSize::try_from(relative_offset).unwrap() + range.start(),
current_char, current_char,
@ -280,9 +281,9 @@ fn ambiguous_unicode_character(
} else if current_char.is_ascii() { } else if current_char.is_ascii() {
// The current word contains at least one ASCII character. // The current word contains at least one ASCII character.
word_flags |= WordFlags::ASCII; word_flags |= WordFlags::ASCII;
} else if let Some(representant) = confusable(current_char as u32) } else if let Some(representant) = confusable(current_char as u32).filter(|representant| {
.filter(|representant| settings.preview.is_enabled() || representant.is_ascii()) is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
{ }) {
// The current word contains an ambiguous unicode character. // The current word contains an ambiguous unicode character.
word_candidates.push(Candidate::new( word_candidates.push(Candidate::new(
TextSize::try_from(relative_offset).unwrap() + range.start(), TextSize::try_from(relative_offset).unwrap() + range.start(),

View File

@ -5,6 +5,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet; use crate::fix::snippet::SourceCodeSnippet;
use crate::preview::is_support_slices_in_literal_concatenation_enabled;
/// ## What it does /// ## What it does
/// Checks for uses of the `+` operator to concatenate collections. /// Checks for uses of the `+` operator to concatenate collections.
@ -197,7 +198,8 @@ pub(crate) fn collection_literal_concatenation(checker: &Checker, expr: &Expr) {
return; 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 { let Some((new_expr, type_)) = concatenate_expressions(expr, should_support_slices) else {
return; return;