From 867c7bda81ee2a7df04d34fb3232676598d9eaf1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 17 May 2023 22:39:46 -0400 Subject: [PATCH] Invert quote-style when generating code within f-strings --- .../test/fixtures/pyupgrade/UP018.py | 1 + .../resources/test/fixtures/ruff/RUF005.py | 3 ++ crates/ruff/src/checkers/ast/mod.rs | 50 ++++++++++++++++++- .../rules/hardcoded_sql_expression.rs | 8 +-- .../rules/request_without_timeout.rs | 2 +- .../flake8_bugbear/rules/assert_false.rs | 2 +- .../rules/duplicate_exceptions.rs | 4 +- .../rules/getattr_with_constant.rs | 2 +- .../redundant_tuple_in_exception_handler.rs | 4 +- .../rules/setattr_with_constant.rs | 8 +-- crates/ruff/src/rules/flake8_errmsg/rules.rs | 21 +++++--- crates/ruff/src/rules/flake8_pie/rules.rs | 4 +- .../rules/duplicate_union_member.rs | 4 +- .../flake8_pytest_style/rules/assertion.rs | 2 +- .../flake8_pytest_style/rules/parametrize.rs | 12 ++--- .../flake8_simplify/rules/ast_bool_op.rs | 37 +++++++------- .../rules/flake8_simplify/rules/ast_expr.rs | 2 +- .../src/rules/flake8_simplify/rules/ast_if.rs | 10 ++-- .../rules/flake8_simplify/rules/ast_ifexp.rs | 16 +++--- .../flake8_simplify/rules/ast_unary_op.rs | 18 +++---- .../rules/reimplemented_builtin.rs | 10 ++-- .../flake8_tidy_imports/relative_imports.rs | 8 +-- .../flynt/rules/static_join_to_fstring.rs | 2 +- crates/ruff/src/rules/pycodestyle/helpers.rs | 11 ++-- .../pycodestyle/rules/lambda_assignment.rs | 27 ++++++---- .../pycodestyle/rules/literal_comparisons.rs | 2 +- .../src/rules/pycodestyle/rules/not_tests.rs | 14 +++++- .../src/rules/pyflakes/rules/repeated_keys.rs | 2 +- .../pylint/rules/compare_to_empty_string.rs | 8 +-- .../pylint/rules/comparison_of_constant.rs | 4 +- .../pylint/rules/magic_value_comparison.rs | 2 +- .../rules/pylint/rules/manual_import_from.rs | 2 +- .../src/rules/pylint/rules/nested_min_max.rs | 2 +- .../rules/pylint/rules/redefined_loop_name.rs | 2 +- .../pylint/rules/repeated_isinstance_calls.rs | 6 +-- ...convert_named_tuple_functional_to_class.rs | 11 ++-- .../convert_typed_dict_functional_to_class.rs | 8 +-- .../rules/lru_cache_without_parameters.rs | 2 +- .../rules/pyupgrade/rules/native_literals.rs | 20 +++----- .../rules/pyupgrade/rules/os_error_alias.rs | 2 +- .../pyupgrade/rules/use_pep604_annotation.rs | 6 +-- .../pyupgrade/rules/use_pep604_isinstance.rs | 4 +- ...ff__rules__pyupgrade__tests__UP018.py.snap | 19 +++++++ .../rules/collection_literal_concatenation.rs | 4 +- ..._rules__ruff__tests__RUF005_RUF005.py.snap | 2 + crates/ruff_python_ast/src/helpers.rs | 11 ++-- .../src/source_code/indexer.rs | 36 ++++++++++--- crates/ruff_python_ast/src/source_code/mod.rs | 2 +- crates/ruff_python_semantic/src/context.rs | 5 ++ 49 files changed, 281 insertions(+), 163 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP018.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP018.py index 4f97f3a0bb6d68..4b15a416a3c69c 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP018.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP018.py @@ -25,3 +25,4 @@ bytes(b"foo") bytes(b""" foo""") +f"{str()}" diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF005.py b/crates/ruff/resources/test/fixtures/ruff/RUF005.py index d0a611c26ddc99..e8ecc06b16e617 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF005.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF005.py @@ -43,3 +43,6 @@ def yay(self): [] + foo + [ # This will be preserved, but doesn't prevent the fix ] + +# Uses the non-preferred quote style, which should be retained. +f"{[*a(), 'b']}" diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 7f182d450d9b6c..ef0084703af538 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -13,7 +13,8 @@ use rustpython_parser::ast::{ use ruff_diagnostics::{Diagnostic, Fix}; use ruff_python_ast::all::{extract_all_names, AllNamesFlags}; use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path}; -use ruff_python_ast::source_code::{Indexer, Locator, Stylist}; +use ruff_python_ast::source_code::{Generator, Indexer, Locator, Quote, Stylist}; +use ruff_python_ast::str::trailing_quote; use ruff_python_ast::types::{Node, RefEquality}; use ruff_python_ast::typing::{parse_type_annotation, AnnotationKind}; use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor}; @@ -134,6 +135,53 @@ impl<'a> Checker<'a> { } noqa::rule_is_ignored(code, offset, self.noqa_line_for, self.locator) } + + /// Create a [`Generator`] to generate source code based on the current AST state. + pub fn generator(&self) -> Generator { + fn quote_style(context: &Context, locator: &Locator, indexer: &Indexer) -> Option { + if !context.in_f_string() { + return None; + } + + let Some(expr) = context.expr() else { + return None; + }; + + // Find the f-string containing the current expression. + let start = expr.start(); + let string_ranges = indexer.f_string_ranges(); + let Ok(string_range_index) = string_ranges.binary_search_by(|range| { + if start < range.start() { + std::cmp::Ordering::Greater + } else if range.contains(start) { + std::cmp::Ordering::Equal + } else { + std::cmp::Ordering::Less + } + }) else { + return None; + }; + let string_range = string_ranges[string_range_index]; + + // Find the quote character used to start the f-string. + let Some(trailing_quote) = trailing_quote(locator.slice(string_range)) else { + return None; + }; + + // Invert the quote character, if it's a single quote. + match *trailing_quote { + "'" => Some(Quote::Double), + "\"" => Some(Quote::Single), + _ => None, + } + } + + Generator::new( + self.stylist.indentation(), + quote_style(&self.ctx, self.locator, self.indexer).unwrap_or(self.stylist.quote()), + self.stylist.line_ending(), + ) + } } impl<'a, 'b> Visitor<'b> for Checker<'a> diff --git a/crates/ruff/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index 66ebe96ece3cd3..5e322d9e3ad763 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -62,14 +62,14 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio }) => { let Some(parent) = checker.ctx.expr_parent() else { if any_over_expr(expr, &has_string_literal) { - return Some(unparse_expr(expr, checker.stylist)); + return Some(unparse_expr(expr, checker.generator())); } return None; }; // Only evaluate the full BinOp, not the nested components. let Expr::BinOp(_ )= parent else { if any_over_expr(expr, &has_string_literal) { - return Some(unparse_expr(expr, checker.stylist)); + return Some(unparse_expr(expr, checker.generator())); } return None; }; @@ -81,12 +81,12 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio }; // "select * from table where val = {}".format(...) if attr == "format" && string_literal(value).is_some() { - return Some(unparse_expr(expr, checker.stylist)); + return Some(unparse_expr(expr, checker.generator())); }; None } // f"select * from table where val = {val}" - Expr::JoinedStr(_) => Some(unparse_expr(expr, checker.stylist)), + Expr::JoinedStr(_) => Some(unparse_expr(expr, checker.generator())), _ => None, } } diff --git a/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs index 2c12c79e33dc4d..50229bd6d25c61 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -48,7 +48,7 @@ pub(crate) fn request_without_timeout( Expr::Constant(ast::ExprConstant { value: value @ Constant::None, .. - }) => Some(unparse_constant(value, checker.stylist)), + }) => Some(unparse_constant(value, checker.generator())), _ => None, } { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs b/crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs index 68e11a885f6e9b..86b69766549cc8 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs @@ -56,7 +56,7 @@ pub(crate) fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_stmt(&assertion_error(msg), checker.stylist), + unparse_stmt(&assertion_error(msg), checker.generator()), stmt.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index 38031fecbcfc7d..721b3b30eae82b 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -97,9 +97,9 @@ fn duplicate_handler_exceptions<'a>( #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( if unique_elts.len() == 1 { - unparse_expr(unique_elts[0], checker.stylist) + unparse_expr(unique_elts[0], checker.generator()) } else { - unparse_expr(&type_pattern(unique_elts), checker.stylist) + unparse_expr(&type_pattern(unique_elts), checker.generator()) }, expr.range(), ))); diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs b/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs index 5ea2332ac1f8b1..a4b80ee933920b 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/getattr_with_constant.rs @@ -69,7 +69,7 @@ pub(crate) fn getattr_with_constant( if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&attribute(obj, value), checker.stylist), + unparse_expr(&attribute(obj, value), checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs b/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs index 17892dd79ec68f..1576d5295ed504 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs @@ -45,14 +45,14 @@ pub(crate) fn redundant_tuple_in_exception_handler( }; let mut diagnostic = Diagnostic::new( RedundantTupleInExceptionHandler { - name: unparse_expr(elt, checker.stylist), + name: unparse_expr(elt, checker.generator()), }, type_.range(), ); if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(elt, checker.stylist), + unparse_expr(elt, checker.generator()), type_.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs b/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs index 4a07e4e3854a26..8a2ab65e57ce6c 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/setattr_with_constant.rs @@ -4,7 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged, Stmt}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::unparse_stmt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; use crate::checkers::ast::Checker; @@ -27,7 +27,7 @@ impl AlwaysAutofixableViolation for SetAttrWithConstant { } } -fn assignment(obj: &Expr, name: &str, value: &Expr, stylist: &Stylist) -> String { +fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> String { let stmt = Stmt::Assign(ast::StmtAssign { targets: vec![Expr::Attribute(ast::ExprAttribute { value: Box::new(obj.clone()), @@ -39,7 +39,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, stylist: &Stylist) -> String type_comment: None, range: TextRange::default(), }); - unparse_stmt(&stmt, stylist) + unparse_stmt(&stmt, generator) } /// B010 @@ -84,7 +84,7 @@ pub(crate) fn setattr_with_constant( if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - assignment(obj, name, value, checker.stylist), + assignment(obj, name, value, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_errmsg/rules.rs b/crates/ruff/src/rules/flake8_errmsg/rules.rs index 242b9ef9025efc..b0eb37b3f5626f 100644 --- a/crates/ruff/src/rules/flake8_errmsg/rules.rs +++ b/crates/ruff/src/rules/flake8_errmsg/rules.rs @@ -4,7 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged, Stmt}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::unparse_stmt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::{Generator, Stylist}; use ruff_python_ast::whitespace; use crate::checkers::ast::Checker; @@ -183,7 +183,13 @@ impl Violation for DotFormatInException { /// 1. Insert the exception argument into a variable assignment before the /// `raise` statement. The variable name is `msg`. /// 2. Replace the exception argument with the variable name. -fn generate_fix(stylist: &Stylist, stmt: &Stmt, exc_arg: &Expr, indentation: &str) -> Fix { +fn generate_fix( + stmt: &Stmt, + exc_arg: &Expr, + indentation: &str, + stylist: &Stylist, + generator: Generator, +) -> Fix { let node = Expr::Name(ast::ExprName { id: "msg".into(), ctx: ExprContext::Store, @@ -195,7 +201,7 @@ fn generate_fix(stylist: &Stylist, stmt: &Stmt, exc_arg: &Expr, indentation: &st type_comment: None, range: TextRange::default(), }); - let assignment = unparse_stmt(&node1, stylist); + let assignment = unparse_stmt(&node1, generator); #[allow(deprecated)] Fix::unspecified_edits( Edit::insertion( @@ -239,10 +245,11 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr if let Some(indentation) = indentation { if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(generate_fix( - checker.stylist, stmt, first, indentation, + checker.stylist, + checker.generator(), )); } } @@ -266,10 +273,11 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr if let Some(indentation) = indentation { if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(generate_fix( - checker.stylist, stmt, first, indentation, + checker.stylist, + checker.generator(), )); } } @@ -296,10 +304,11 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr if let Some(indentation) = indentation { if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(generate_fix( - checker.stylist, stmt, first, indentation, + checker.stylist, + checker.generator(), )); } } diff --git a/crates/ruff/src/rules/flake8_pie/rules.rs b/crates/ruff/src/rules/flake8_pie/rules.rs index 5dae8ec66d8039..040c5d7769fdc8 100644 --- a/crates/ruff/src/rules/flake8_pie/rules.rs +++ b/crates/ruff/src/rules/flake8_pie/rules.rs @@ -443,7 +443,7 @@ pub(crate) fn non_unique_enums<'a, 'b>( if !seen_targets.insert(ComparableExpr::from(value)) { let diagnostic = Diagnostic::new( NonUniqueEnums { - value: unparse_expr(value, checker.stylist), + value: unparse_expr(value, checker.generator()), }, stmt.range(), ); @@ -612,7 +612,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) { let bool_op = node; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&bool_op, checker.stylist), + unparse_expr(&bool_op, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 1a10df206f9b5d..d5e7ff983bbf55 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -61,7 +61,7 @@ fn traverse_union<'a>( if !seen_nodes.insert(expr.into()) { let mut diagnostic = Diagnostic::new( DuplicateUnionMember { - duplicate_name: unparse_expr(expr, checker.stylist), + duplicate_name: unparse_expr(expr, checker.generator()), }, expr.range(), ); @@ -82,7 +82,7 @@ fn traverse_union<'a>( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( unparse_expr( if expr == left.as_ref() { right } else { left }, - checker.stylist, + checker.generator(), ), parent.range(), ))); diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/assertion.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/assertion.rs index 85776c25eeb743..721844d95f0aec 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/assertion.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/assertion.rs @@ -198,7 +198,7 @@ pub(crate) fn unittest_assertion( if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_stmt(&stmt, checker.stylist), + unparse_stmt(&stmt, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs index d996eaf24ad9e6..14b045522aa791 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -78,7 +78,7 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option { kind: None, range: TextRange::default(), }); - Some(unparse_expr(&node, checker.stylist)) + Some(unparse_expr(&node, checker.generator())) } /// Returns the range of the `name` argument of `@pytest.mark.parametrize`. @@ -164,7 +164,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) { }); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - format!("({})", unparse_expr(&node, checker.stylist,)), + format!("({})", unparse_expr(&node, checker.generator())), name_range, ))); } @@ -195,7 +195,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) { }); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node, checker.stylist), + unparse_expr(&node, checker.generator()), name_range, ))); } @@ -228,7 +228,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) { }); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node, checker.stylist), + unparse_expr(&node, checker.generator()), expr.range(), ))); } @@ -278,7 +278,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) { }); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - format!("({})", unparse_expr(&node, checker.stylist,)), + format!("({})", unparse_expr(&node, checker.generator())), expr.range(), ))); } @@ -373,7 +373,7 @@ fn handle_single_name(checker: &mut Checker, expr: &Expr, value: &Expr) { let node = value.clone(); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node, checker.stylist), + unparse_expr(&node, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_bool_op.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_bool_op.rs index 85285e00a7ac9d..a7bfb6f7a56b08 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_bool_op.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_bool_op.rs @@ -11,8 +11,6 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, AutofixKind, Diagnostic, Edit use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::{contains_effect, has_comments, unparse_expr, Truthiness}; -use ruff_python_ast::source_code::Stylist; -use ruff_python_semantic::context::Context; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -370,7 +368,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) { // multiple duplicates, the fixes will conflict. #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&bool_op, checker.stylist), + unparse_expr(&bool_op, checker.generator()), expr.range(), ))); } @@ -457,7 +455,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) { let in_expr = node2.into(); let mut diagnostic = Diagnostic::new( CompareWithTuple { - replacement: unparse_expr(&in_expr, checker.stylist), + replacement: unparse_expr(&in_expr, checker.generator()), }, expr.range(), ); @@ -481,7 +479,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) { }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&in_expr, checker.stylist), + unparse_expr(&in_expr, checker.generator()), expr.range(), ))); } @@ -604,7 +602,7 @@ pub(crate) fn get_short_circuit_edit( range: TextRange, truthiness: Truthiness, in_boolean_test: bool, - stylist: &Stylist, + checker: &Checker, ) -> Edit { let content = if in_boolean_test { match truthiness { @@ -615,7 +613,7 @@ pub(crate) fn get_short_circuit_edit( } } } else { - unparse_expr(expr, stylist) + unparse_expr(expr, checker.generator()) }; Edit::range_replacement(content, range) } @@ -623,8 +621,7 @@ pub(crate) fn get_short_circuit_edit( fn is_short_circuit( expr: &Expr, expected_op: Boolop, - context: &Context, - stylist: &Stylist, + checker: &Checker, ) -> Option<(Edit, ContentAround)> { let Expr::BoolOp(ast::ExprBoolOp { op, values, range: _, }) = expr else { return None; @@ -643,12 +640,14 @@ fn is_short_circuit( for (index, (value, next_value)) in values.iter().tuple_windows().enumerate() { // Keep track of the location of the furthest-right, truthy or falsey expression. - let value_truthiness = Truthiness::from_expr(value, |id| context.is_builtin(id)); - let next_value_truthiness = Truthiness::from_expr(next_value, |id| context.is_builtin(id)); + let value_truthiness = Truthiness::from_expr(value, |id| checker.ctx.is_builtin(id)); + let next_value_truthiness = + Truthiness::from_expr(next_value, |id| checker.ctx.is_builtin(id)); // Keep track of the location of the furthest-right, non-effectful expression. if value_truthiness.is_unknown() - && (!context.in_boolean_test() || contains_effect(value, |id| context.is_builtin(id))) + && (!checker.ctx.in_boolean_test() + || contains_effect(value, |id| checker.ctx.is_builtin(id))) { location = next_value.start(); continue; @@ -668,8 +667,8 @@ fn is_short_circuit( value, TextRange::new(location, expr.end()), short_circuit_truthiness, - context.in_boolean_test(), - stylist, + checker.ctx.in_boolean_test(), + checker, )); break; } @@ -686,8 +685,8 @@ fn is_short_circuit( next_value, TextRange::new(location, expr.end()), short_circuit_truthiness, - context.in_boolean_test(), - stylist, + checker.ctx.in_boolean_test(), + checker, )); break; } @@ -701,8 +700,7 @@ fn is_short_circuit( /// SIM222 pub(crate) fn expr_or_true(checker: &mut Checker, expr: &Expr) { - if let Some((edit, remove)) = is_short_circuit(expr, Boolop::Or, &checker.ctx, checker.stylist) - { + if let Some((edit, remove)) = is_short_circuit(expr, Boolop::Or, checker) { let mut diagnostic = Diagnostic::new( ExprOrTrue { expr: edit.content().unwrap_or_default().to_string(), @@ -720,8 +718,7 @@ pub(crate) fn expr_or_true(checker: &mut Checker, expr: &Expr) { /// SIM223 pub(crate) fn expr_and_false(checker: &mut Checker, expr: &Expr) { - if let Some((edit, remove)) = is_short_circuit(expr, Boolop::And, &checker.ctx, checker.stylist) - { + if let Some((edit, remove)) = is_short_circuit(expr, Boolop::And, checker) { let mut diagnostic = Diagnostic::new( ExprAndFalse { expr: edit.content().unwrap_or_default().to_string(), diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs index 399621a6b684eb..cc25435ae0765a 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_expr.rs @@ -149,7 +149,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) { let new_env_var = node.into(); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&new_env_var, checker.stylist), + unparse_expr(&new_env_var, checker.generator()), slice.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs index 2094271f205898..04e7490437647b 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_if.rs @@ -348,7 +348,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) { return; } - let condition = unparse_expr(test, checker.stylist); + let condition = unparse_expr(test, checker.generator()); let fixable = matches!(if_return, Bool::True) && matches!(else_return, Bool::False) && !has_comments(stmt, checker.locator) @@ -364,7 +364,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) { }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_stmt(&node.into(), checker.stylist), + unparse_stmt(&node.into(), checker.generator()), stmt.range(), ))); } else { @@ -387,7 +387,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) { }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_stmt(&node2.into(), checker.stylist), + unparse_stmt(&node2.into(), checker.generator()), stmt.range(), ))); }; @@ -504,7 +504,7 @@ pub(crate) fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: O let target_var = &body_targets[0]; let ternary = ternary(target_var, body_value, test, orelse_value); - let contents = unparse_stmt(&ternary, checker.stylist); + let contents = unparse_stmt(&ternary, checker.generator()); // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); @@ -859,7 +859,7 @@ pub(crate) fn use_dict_get_with_default( type_comment: None, range: TextRange::default(), }; - let contents = unparse_stmt(&node5.into(), checker.stylist); + let contents = unparse_stmt(&node5.into(), checker.generator()); // Don't flag if the resulting expression would exceed the maximum line length. let line_start = checker.locator.line_start(stmt.start()); diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_ifexp.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_ifexp.rs index 185013bf0efae3..3d3c2d0d5920ab 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_ifexp.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_ifexp.rs @@ -97,7 +97,7 @@ pub(crate) fn explicit_true_false_in_ifexpr( let mut diagnostic = Diagnostic::new( IfExprWithTrueFalse { - expr: unparse_expr(test, checker.stylist), + expr: unparse_expr(test, checker.generator()), }, expr.range(), ); @@ -105,7 +105,7 @@ pub(crate) fn explicit_true_false_in_ifexpr( if matches!(test, Expr::Compare(_)) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&test.clone(), checker.stylist), + unparse_expr(&test.clone(), checker.generator()), expr.range(), ))); } else if checker.ctx.is_builtin("bool") { @@ -122,7 +122,7 @@ pub(crate) fn explicit_true_false_in_ifexpr( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node1.into(), checker.stylist), + unparse_expr(&node1.into(), checker.generator()), expr.range(), ))); }; @@ -153,7 +153,7 @@ pub(crate) fn explicit_false_true_in_ifexpr( let mut diagnostic = Diagnostic::new( IfExprWithFalseTrue { - expr: unparse_expr(test, checker.stylist), + expr: unparse_expr(test, checker.generator()), }, expr.range(), ); @@ -166,7 +166,7 @@ pub(crate) fn explicit_false_true_in_ifexpr( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node1.into(), checker.stylist), + unparse_expr(&node1.into(), checker.generator()), expr.range(), ))); } @@ -201,8 +201,8 @@ pub(crate) fn twisted_arms_in_ifexpr( let mut diagnostic = Diagnostic::new( IfExprWithTwistedArms { - expr_body: unparse_expr(body, checker.stylist), - expr_else: unparse_expr(orelse, checker.stylist), + expr_body: unparse_expr(body, checker.generator()), + expr_else: unparse_expr(orelse, checker.generator()), }, expr.range(), ); @@ -218,7 +218,7 @@ pub(crate) fn twisted_arms_in_ifexpr( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node3.into(), checker.stylist), + unparse_expr(&node3.into(), checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs index c37f56434571f3..e804cd0c1e6f3c 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -107,8 +107,8 @@ pub(crate) fn negation_with_equal_op( let mut diagnostic = Diagnostic::new( NegateEqualOp { - left: unparse_expr(left, checker.stylist), - right: unparse_expr(&comparators[0], checker.stylist), + left: unparse_expr(left, checker.generator()), + right: unparse_expr(&comparators[0], checker.generator()), }, expr.range(), ); @@ -121,7 +121,7 @@ pub(crate) fn negation_with_equal_op( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node.into(), checker.stylist), + unparse_expr(&node.into(), checker.generator()), expr.range(), ))); } @@ -157,8 +157,8 @@ pub(crate) fn negation_with_not_equal_op( let mut diagnostic = Diagnostic::new( NegateNotEqualOp { - left: unparse_expr(left, checker.stylist), - right: unparse_expr(&comparators[0], checker.stylist), + left: unparse_expr(left, checker.generator()), + right: unparse_expr(&comparators[0], checker.generator()), }, expr.range(), ); @@ -171,7 +171,7 @@ pub(crate) fn negation_with_not_equal_op( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node.into(), checker.stylist), + unparse_expr(&node.into(), checker.generator()), expr.range(), ))); } @@ -192,7 +192,7 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: Unaryop, o let mut diagnostic = Diagnostic::new( DoubleNegation { - expr: unparse_expr(operand, checker.stylist), + expr: unparse_expr(operand, checker.generator()), }, expr.range(), ); @@ -200,7 +200,7 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: Unaryop, o if checker.ctx.in_boolean_test() { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(operand, checker.stylist), + unparse_expr(operand, checker.generator()), expr.range(), ))); } else if checker.ctx.is_builtin("bool") { @@ -217,7 +217,7 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: Unaryop, o }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&node1.into(), checker.stylist), + unparse_expr(&node1.into(), checker.generator()), expr.range(), ))); }; diff --git a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index 587c83d5904ba4..940a2659300cb1 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -7,7 +7,7 @@ use unicode_width::UnicodeWidthStr; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::unparse_stmt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use crate::checkers::ast::Checker; use crate::registry::{AsRule, Rule}; @@ -171,7 +171,7 @@ fn return_values_for_siblings<'a>(stmt: &'a Stmt, sibling: &'a Stmt) -> Option String { +fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Generator) -> String { let node = ast::ExprGeneratorExp { elt: Box::new(test.clone()), generators: vec![Comprehension { @@ -198,7 +198,7 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, stylist: &Styl value: Some(Box::new(node2.into())), range: TextRange::default(), }; - unparse_stmt(&node3.into(), stylist) + unparse_stmt(&node3.into(), generator) } /// SIM110, SIM111 @@ -220,7 +220,7 @@ pub(crate) fn convert_for_loop_to_any_all( loop_info.test, loop_info.target, loop_info.iter, - checker.stylist, + checker.generator(), ); // Don't flag if the resulting expression would exceed the maximum line length. @@ -310,7 +310,7 @@ pub(crate) fn convert_for_loop_to_any_all( &test, loop_info.target, loop_info.iter, - checker.stylist, + checker.generator(), ); // Don't flag if the resulting expression would exceed the maximum line length. diff --git a/crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs b/crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs index 9d1b284c311832..35e1317c7ec79b 100644 --- a/crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs +++ b/crates/ruff/src/rules/flake8_tidy_imports/relative_imports.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation, CacheKey}; use ruff_python_ast::helpers::{resolve_imported_module_path, unparse_stmt}; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; @@ -90,7 +90,7 @@ fn fix_banned_relative_import( level: Option, module: Option<&str>, module_path: Option<&[String]>, - stylist: &Stylist, + generator: Generator, ) -> Option { // Only fix is the module path is known. let Some(module_path) = resolve_imported_module_path(level, module, module_path) else { @@ -112,7 +112,7 @@ fn fix_banned_relative_import( level: Some(Int::new(0)), range: TextRange::default(), }; - let content = unparse_stmt(&node.into(), stylist); + let content = unparse_stmt(&node.into(), generator); #[allow(deprecated)] Some(Fix::unspecified(Edit::range_replacement( content, @@ -142,7 +142,7 @@ pub fn banned_relative_import( ); if checker.patch(diagnostic.kind.rule()) { if let Some(fix) = - fix_banned_relative_import(stmt, level, module, module_path, checker.stylist) + fix_banned_relative_import(stmt, level, module, module_path, checker.generator()) { diagnostic.set_fix(fix); }; diff --git a/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs index b883d03c6baaf3..7332175cd2442d 100644 --- a/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff/src/rules/flynt/rules/static_join_to_fstring.rs @@ -80,7 +80,7 @@ pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner: // convertible to f-string parts). let Some(new_expr) = build_fstring(joiner, joinees) else { return }; - let contents = unparse_expr(&new_expr, checker.stylist); + let contents = unparse_expr(&new_expr, checker.generator()); let mut diagnostic = Diagnostic::new( StaticJoinToFString { diff --git a/crates/ruff/src/rules/pycodestyle/helpers.rs b/crates/ruff/src/rules/pycodestyle/helpers.rs index d3766abd974342..4c328b79179b87 100644 --- a/crates/ruff/src/rules/pycodestyle/helpers.rs +++ b/crates/ruff/src/rules/pycodestyle/helpers.rs @@ -1,10 +1,11 @@ -use ruff_python_ast::helpers::unparse_expr; -use ruff_python_ast::newlines::Line; -use ruff_python_ast::source_code::Stylist; use ruff_text_size::{TextLen, TextRange}; use rustpython_parser::ast::{self, Cmpop, Expr}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use ruff_python_ast::helpers::unparse_expr; +use ruff_python_ast::newlines::Line; +use ruff_python_ast::source_code::Generator; + pub(crate) fn is_ambiguous_name(name: &str) -> bool { name == "l" || name == "I" || name == "O" } @@ -13,7 +14,7 @@ pub(crate) fn compare( left: &Expr, ops: &[Cmpop], comparators: &[Expr], - stylist: &Stylist, + generator: Generator, ) -> String { let node = ast::ExprCompare { left: Box::new(left.clone()), @@ -21,7 +22,7 @@ pub(crate) fn compare( comparators: comparators.to_vec(), range: TextRange::default(), }; - unparse_expr(&node.into(), stylist) + unparse_expr(&node.into(), generator) } pub(super) fn is_overlong( diff --git a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs index 5256c2e24cecaa..088bda4c2a8d97 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -1,13 +1,14 @@ +use ruff_text_size::TextRange; +use rustpython_parser::ast::{self, Arg, Arguments, Constant, Expr, Ranged, Stmt}; + use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::{has_leading_content, has_trailing_content, unparse_stmt}; use ruff_python_ast::newlines::StrExt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use ruff_python_ast::whitespace::leading_space; use ruff_python_semantic::context::Context; use ruff_python_semantic::scope::ScopeKind; -use ruff_text_size::TextRange; -use rustpython_parser::ast::{self, Arg, Arguments, Constant, Expr, Ranged, Stmt}; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -88,10 +89,16 @@ pub(crate) fn lambda_assignment( let first_line = checker.locator.line(stmt.start()); let indentation = &leading_space(first_line); let mut indented = String::new(); - for (idx, line) in - function(&checker.ctx, id, args, body, annotation, checker.stylist) - .universal_newlines() - .enumerate() + for (idx, line) in function( + &checker.ctx, + id, + args, + body, + annotation, + checker.generator(), + ) + .universal_newlines() + .enumerate() { if idx == 0 { indented.push_str(&line); @@ -158,7 +165,7 @@ fn function( args: &Arguments, body: &Expr, annotation: Option<&Expr>, - stylist: &Stylist, + generator: Generator, ) -> String { let body = Stmt::Return(ast::StmtReturn { value: Some(Box::new(body.clone())), @@ -203,7 +210,7 @@ fn function( type_comment: None, range: TextRange::default(), }); - return unparse_stmt(&func, stylist); + return unparse_stmt(&func, generator); } } let func = Stmt::FunctionDef(ast::StmtFunctionDef { @@ -215,5 +222,5 @@ fn function( type_comment: None, range: TextRange::default(), }); - unparse_stmt(&func, stylist) + unparse_stmt(&func, generator) } diff --git a/crates/ruff/src/rules/pycodestyle/rules/literal_comparisons.rs b/crates/ruff/src/rules/pycodestyle/rules/literal_comparisons.rs index 94a5bfe34f70a5..7658e9ad67e3d1 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/literal_comparisons.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/literal_comparisons.rs @@ -278,7 +278,7 @@ pub(crate) fn literal_comparisons( .map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op)) .copied() .collect::>(); - let content = compare(left, &ops, comparators, checker.stylist); + let content = compare(left, &ops, comparators, checker.generator()); for diagnostic in &mut diagnostics { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diff --git a/crates/ruff/src/rules/pycodestyle/rules/not_tests.rs b/crates/ruff/src/rules/pycodestyle/rules/not_tests.rs index 971856e9d7c34f..3c975789b8d30f 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/not_tests.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/not_tests.rs @@ -101,7 +101,12 @@ pub(crate) fn not_tests( if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - compare(left, &[Cmpop::NotIn], comparators, checker.stylist), + compare( + left, + &[Cmpop::NotIn], + comparators, + checker.generator(), + ), expr.range(), ))); } @@ -114,7 +119,12 @@ pub(crate) fn not_tests( if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - compare(left, &[Cmpop::IsNot], comparators, checker.stylist), + compare( + left, + &[Cmpop::IsNot], + comparators, + checker.generator(), + ), expr.range(), ))); } diff --git a/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs b/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs index 09e40ab42ea196..0be958c6b38c71 100644 --- a/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs +++ b/crates/ruff/src/rules/pyflakes/rules/repeated_keys.rs @@ -106,7 +106,7 @@ pub(crate) fn repeated_keys(checker: &mut Checker, keys: &[Option], values let is_duplicate_value = seen_values.contains(&comparable_value); let mut diagnostic = Diagnostic::new( MultiValueRepeatedKeyLiteral { - name: unparse_expr(key, checker.stylist), + name: unparse_expr(key, checker.generator()), repeated_value: is_duplicate_value, }, key.range(), diff --git a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs index 7b9cc253b06a6a..de73b561439def 100644 --- a/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs +++ b/crates/ruff/src/rules/pylint/rules/compare_to_empty_string.rs @@ -117,8 +117,8 @@ pub(crate) fn compare_to_empty_string( if let Expr::Constant(ast::ExprConstant { value, .. }) = &lhs { if let Constant::Str(s) = value { if s.is_empty() { - let constant = unparse_constant(value, checker.stylist); - let expr = unparse_expr(rhs, checker.stylist); + let constant = unparse_constant(value, checker.generator()); + let expr = unparse_expr(rhs, checker.generator()); let existing = format!("{constant} {op} {expr}"); let replacement = format!("{}{expr}", op.into_unary()); checker.diagnostics.push(Diagnostic::new( @@ -137,8 +137,8 @@ pub(crate) fn compare_to_empty_string( if let Expr::Constant(ast::ExprConstant { value, .. }) = &rhs { if let Constant::Str(s) = value { if s.is_empty() { - let expr = unparse_expr(lhs, checker.stylist); - let constant = unparse_constant(value, checker.stylist); + let expr = unparse_expr(lhs, checker.generator()); + let constant = unparse_constant(value, checker.generator()); let existing = format!("{expr} {op} {constant}"); let replacement = format!("{}{expr}", op.into_unary()); checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff/src/rules/pylint/rules/comparison_of_constant.rs b/crates/ruff/src/rules/pylint/rules/comparison_of_constant.rs index afe30820d92111..24e3db500c0139 100644 --- a/crates/ruff/src/rules/pylint/rules/comparison_of_constant.rs +++ b/crates/ruff/src/rules/pylint/rules/comparison_of_constant.rs @@ -106,9 +106,9 @@ pub(crate) fn comparison_of_constant( { let diagnostic = Diagnostic::new( ComparisonOfConstant { - left_constant: unparse_constant(left_constant, checker.stylist), + left_constant: unparse_constant(left_constant, checker.generator()), op: op.into(), - right_constant: unparse_constant(right_constant, checker.stylist), + right_constant: unparse_constant(right_constant, checker.generator()), }, left.range(), ); diff --git a/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs b/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs index b1810498b81774..ad8d026b6d06ea 100644 --- a/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs +++ b/crates/ruff/src/rules/pylint/rules/magic_value_comparison.rs @@ -79,7 +79,7 @@ pub(crate) fn magic_value_comparison(checker: &mut Checker, left: &Expr, compara if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) { checker.diagnostics.push(Diagnostic::new( MagicValueComparison { - value: unparse_expr(comparison_expr, checker.stylist), + value: unparse_expr(comparison_expr, checker.generator()), }, comparison_expr.range(), )); diff --git a/crates/ruff/src/rules/pylint/rules/manual_import_from.rs b/crates/ruff/src/rules/pylint/rules/manual_import_from.rs index 5ff3697228d0dc..ffe41671ecac93 100644 --- a/crates/ruff/src/rules/pylint/rules/manual_import_from.rs +++ b/crates/ruff/src/rules/pylint/rules/manual_import_from.rs @@ -67,7 +67,7 @@ pub(crate) fn manual_from_import( }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_stmt(&node.into(), checker.stylist), + unparse_stmt(&node.into(), checker.generator()), stmt.range(), ))); } diff --git a/crates/ruff/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff/src/rules/pylint/rules/nested_min_max.rs index a43289160139fe..9f897486358d5e 100644 --- a/crates/ruff/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff/src/rules/pylint/rules/nested_min_max.rs @@ -149,7 +149,7 @@ pub(crate) fn nested_min_max( }); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&flattened_expr, checker.stylist), + unparse_expr(&flattened_expr, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs index 28a0473799c8ad..202677a7e51dd2 100644 --- a/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs +++ b/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs @@ -393,7 +393,7 @@ pub(crate) fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &N { checker.diagnostics.push(Diagnostic::new( RedefinedLoopName { - name: unparse_expr(outer_assignment_target.expr, checker.stylist), + name: unparse_expr(outer_assignment_target.expr, checker.generator()), outer_kind: outer_assignment_target.binding_kind, inner_kind: inner_assignment_target.binding_kind, }, diff --git a/crates/ruff/src/rules/pylint/rules/repeated_isinstance_calls.rs b/crates/ruff/src/rules/pylint/rules/repeated_isinstance_calls.rs index 80be7637268648..6995c200d8fa6d 100644 --- a/crates/ruff/src/rules/pylint/rules/repeated_isinstance_calls.rs +++ b/crates/ruff/src/rules/pylint/rules/repeated_isinstance_calls.rs @@ -12,7 +12,7 @@ use crate::checkers::ast::Checker; #[violation] pub struct RepeatedIsinstanceCalls { obj: String, - pub types: Vec, + types: Vec, } impl Violation for RepeatedIsinstanceCalls { @@ -66,11 +66,11 @@ pub(crate) fn repeated_isinstance_calls( if num_calls > 1 && types.len() > 1 { checker.diagnostics.push(Diagnostic::new( RepeatedIsinstanceCalls { - obj: unparse_expr(obj.as_expr(), checker.stylist), + obj: unparse_expr(obj.as_expr(), checker.generator()), types: types .iter() .map(HashableExpr::as_expr) - .map(|expr| unparse_expr(expr, checker.stylist)) + .map(|expr| unparse_expr(expr, checker.generator())) .sorted() .collect(), }, diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index 744c3e1c7c9be5..699d2324d9d17a 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -6,7 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged, use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::unparse_stmt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; @@ -171,11 +171,14 @@ fn convert_to_class( typename: &str, body: Vec, base_class: &Expr, - stylist: &Stylist, + generator: Generator, ) -> Fix { #[allow(deprecated)] Fix::unspecified(Edit::range_replacement( - unparse_stmt(&create_class_def_stmt(typename, body, base_class), stylist), + unparse_stmt( + &create_class_def_stmt(typename, body, base_class), + generator, + ), stmt.range(), )) } @@ -216,7 +219,7 @@ pub(crate) fn convert_named_tuple_functional_to_class( typename, properties, base_class, - checker.stylist, + checker.generator(), )); } checker.diagnostics.push(diagnostic); diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index 36a0728df6a6bb..9455e9a8162b07 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -6,7 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged, use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::unparse_stmt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::Generator; use ruff_python_stdlib::identifiers::is_identifier; use crate::checkers::ast::Checker; @@ -222,13 +222,13 @@ fn convert_to_class( body: Vec, total_keyword: Option<&Keyword>, base_class: &Expr, - stylist: &Stylist, + generator: Generator, ) -> Fix { #[allow(deprecated)] Fix::unspecified(Edit::range_replacement( unparse_stmt( &create_class_def_stmt(class_name, body, total_keyword, base_class), - stylist, + generator, ), stmt.range(), )) @@ -270,7 +270,7 @@ pub(crate) fn convert_typed_dict_functional_to_class( body, total_keyword, base_class, - checker.stylist, + checker.generator(), )); } checker.diagnostics.push(diagnostic); diff --git a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index 0da4a40499cc48..9f3dcb79102079 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -51,7 +51,7 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(func, checker.stylist), + unparse_expr(func, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs index f99f930c0df9be..4647e6fb9ec64d 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/native_literals.rs @@ -4,6 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::unparse_constant; use ruff_python_ast::str::is_implicit_concatenation; use crate::checkers::ast::Checker; @@ -64,20 +65,15 @@ pub(crate) fn native_literals( LiteralType::Bytes }}, expr.range()); if checker.patch(diagnostic.kind.rule()) { + let constant = if id == "bytes" { + Constant::Bytes(vec![]) + } else { + Constant::Str(String::new()) + }; + let content = unparse_constant(&constant, checker.generator()); #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - if id == "bytes" { - let mut content = String::with_capacity(3); - content.push('b'); - content.push(checker.stylist.quote().into()); - content.push(checker.stylist.quote().into()); - content - } else { - let mut content = String::with_capacity(2); - content.push(checker.stylist.quote().into()); - content.push(checker.stylist.quote().into()); - content - }, + content, expr.range(), ))); } diff --git a/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs index 8d11f320cebb97..c47aac8d1c0619 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/os_error_alias.rs @@ -117,7 +117,7 @@ fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) { }; #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - format!("({})", unparse_expr(&node.into(), checker.stylist,)), + format!("({})", unparse_expr(&node.into(), checker.generator())), target.range(), ))); } diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs index bc49f0376972ed..36855b6bd4164e 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -67,7 +67,7 @@ pub(crate) fn use_pep604_annotation( if fixable && checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&optional(slice), checker.stylist), + unparse_expr(&optional(slice), checker.generator()), expr.range(), ))); } @@ -83,7 +83,7 @@ pub(crate) fn use_pep604_annotation( Expr::Tuple(ast::ExprTuple { elts, .. }) => { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&union(elts), checker.stylist), + unparse_expr(&union(elts), checker.generator()), expr.range(), ))); } @@ -91,7 +91,7 @@ pub(crate) fn use_pep604_annotation( // Single argument. #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(slice, checker.stylist), + unparse_expr(slice, checker.generator()), expr.range(), ))); } diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs index fd761afa79768d..03f5c690d769de 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_isinstance.rs @@ -1,6 +1,6 @@ -use ruff_text_size::TextRange; use std::fmt; +use ruff_text_size::TextRange; use rustpython_parser::ast::{self, Expr, Operator, Ranged}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; @@ -94,7 +94,7 @@ pub(crate) fn use_pep604_isinstance( if checker.patch(diagnostic.kind.rule()) { #[allow(deprecated)] diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - unparse_expr(&union(elts), checker.stylist), + unparse_expr(&union(elts), checker.generator()), types.range(), ))); } diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP018.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP018.py.snap index a7faa63d8a9916..e392df3212121c 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP018.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP018.py.snap @@ -106,6 +106,7 @@ UP018.py:25:1: UP018 [*] Unnecessary call to `bytes` 25 |+b"foo" 26 26 | bytes(b""" 27 27 | foo""") +28 28 | f"{str()}" UP018.py:26:1: UP018 [*] Unnecessary call to `bytes` | @@ -114,6 +115,7 @@ UP018.py:26:1: UP018 [*] Unnecessary call to `bytes` 28 | / bytes(b""" 29 | | foo""") | |_______^ UP018 +30 | f"{str()}" | = help: Replace with `bytes` @@ -125,5 +127,22 @@ UP018.py:26:1: UP018 [*] Unnecessary call to `bytes` 27 |-foo""") 26 |+b""" 27 |+foo""" +28 28 | f"{str()}" + +UP018.py:28:4: UP018 [*] Unnecessary call to `str` + | +28 | bytes(b""" +29 | foo""") +30 | f"{str()}" + | ^^^^^ UP018 + | + = help: Replace with `str` + +ℹ Suggested fix +25 25 | bytes(b"foo") +26 26 | bytes(b""" +27 27 | foo""") +28 |-f"{str()}" + 28 |+f"{''}" diff --git a/crates/ruff/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff/src/rules/ruff/rules/collection_literal_concatenation.rs index e5f6527c119ac5..30c5b9de6057d4 100644 --- a/crates/ruff/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -130,8 +130,8 @@ pub(crate) fn collection_literal_concatenation(checker: &mut Checker, expr: &Exp let contents = match kind { // Wrap the new expression in parentheses if it was a tuple - Kind::Tuple => format!("({})", unparse_expr(&new_expr, checker.stylist)), - Kind::List => unparse_expr(&new_expr, checker.stylist), + Kind::Tuple => format!("({})", unparse_expr(&new_expr, checker.generator())), + Kind::List => unparse_expr(&new_expr, checker.generator()), }; let fixable = !has_comments(expr, checker.locator); diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF005_RUF005.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF005_RUF005.py.snap index 4a0263ba1a300b..bb508f24fa47e5 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF005_RUF005.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF005_RUF005.py.snap @@ -265,5 +265,7 @@ RUF005.py:44:1: RUF005 [*] Consider `[*foo]` instead of concatenation 44 |-[] + foo + [ # This will be preserved, but doesn't prevent the fix 44 |+[*foo] + [ # This will be preserved, but doesn't prevent the fix 45 45 | ] +46 46 | +47 47 | # Uses the non-preferred quote style, which should be retained. diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 31ff4e7941ac6e..0cc38fb7932f5f 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -17,26 +17,23 @@ use smallvec::SmallVec; use crate::call_path::CallPath; use crate::newlines::UniversalNewlineIterator; -use crate::source_code::{Generator, Indexer, Locator, Stylist}; +use crate::source_code::{Generator, Indexer, Locator}; use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor}; /// Generate source code from an [`Expr`]. -pub fn unparse_expr(expr: &Expr, stylist: &Stylist) -> String { - let mut generator: Generator = stylist.into(); +pub fn unparse_expr(expr: &Expr, mut generator: Generator) -> String { generator.unparse_expr(expr, 0); generator.generate() } /// Generate source code from a [`Stmt`]. -pub fn unparse_stmt(stmt: &Stmt, stylist: &Stylist) -> String { - let mut generator: Generator = stylist.into(); +pub fn unparse_stmt(stmt: &Stmt, mut generator: Generator) -> String { generator.unparse_stmt(stmt); generator.generate() } /// Generate source code from an [`Constant`]. -pub fn unparse_constant(constant: &Constant, stylist: &Stylist) -> String { - let mut generator: Generator = stylist.into(); +pub fn unparse_constant(constant: &Constant, mut generator: Generator) -> String { generator.unparse_constant(constant); generator.generate() } diff --git a/crates/ruff_python_ast/src/source_code/indexer.rs b/crates/ruff_python_ast/src/source_code/indexer.rs index dd6c459c0083a1..9587c113245121 100644 --- a/crates/ruff_python_ast/src/source_code/indexer.rs +++ b/crates/ruff_python_ast/src/source_code/indexer.rs @@ -4,7 +4,7 @@ use crate::source_code::Locator; use ruff_text_size::{TextRange, TextSize}; use rustpython_parser::lexer::LexResult; -use rustpython_parser::Tok; +use rustpython_parser::{StringKind, Tok}; pub struct Indexer { /// Stores the ranges of comments sorted by [`TextRange::start`] in increasing order. No two ranges are overlapping. @@ -16,15 +16,20 @@ pub struct Indexer { /// The range of all triple quoted strings in the source document. The ranges are sorted by their /// [`TextRange::start`] position in increasing order. No two ranges are overlapping. triple_quoted_string_ranges: Vec, + + /// The range of all f-string in the source document. The ranges are sorted by their + /// [`TextRange::start`] position in increasing order. No two ranges are overlapping. + f_string_ranges: Vec, } impl Indexer { pub fn from_tokens(tokens: &[LexResult], locator: &Locator) -> Self { assert!(TextSize::try_from(locator.contents().len()).is_ok()); - let mut commented_lines = Vec::new(); + let mut comment_ranges = Vec::new(); let mut continuation_lines = Vec::new(); - let mut string_ranges = Vec::new(); + let mut triple_quoted_string_ranges = Vec::new(); + let mut f_string_ranges = Vec::new(); // Token, end let mut prev_end = TextSize::default(); let mut prev_token: Option<&Tok> = None; @@ -59,15 +64,23 @@ impl Indexer { match tok { Tok::Comment(..) => { - commented_lines.push(*range); + comment_ranges.push(*range); } Tok::Newline | Tok::NonLogicalNewline => { line_start = range.end(); } Tok::String { - triple_quoted: true, + triple_quoted, + kind, .. - } => string_ranges.push(*range), + } => { + if *triple_quoted { + triple_quoted_string_ranges.push(*range); + } + if matches!(kind, StringKind::FString | StringKind::RawFString) { + f_string_ranges.push(*range); + } + } _ => {} } @@ -75,9 +88,10 @@ impl Indexer { prev_end = range.end(); } Self { - comment_ranges: commented_lines, + comment_ranges, continuation_lines, - triple_quoted_string_ranges: string_ranges, + triple_quoted_string_ranges, + f_string_ranges, } } @@ -97,6 +111,12 @@ impl Indexer { &self.triple_quoted_string_ranges } + /// Return a slice of all ranges that include an f- string. The ranges are sorted by + /// [`TextRange::start`] in increasing order. No two ranges are overlapping. + pub fn f_string_ranges(&self) -> &[TextRange] { + &self.f_string_ranges + } + pub fn is_continuation(&self, offset: TextSize, locator: &Locator) -> bool { let line_start = locator.line_start(offset); self.continuation_lines.binary_search(&line_start).is_ok() diff --git a/crates/ruff_python_ast/src/source_code/mod.rs b/crates/ruff_python_ast/src/source_code/mod.rs index 3a33274137c109..3a1764d3d0d9fd 100644 --- a/crates/ruff_python_ast/src/source_code/mod.rs +++ b/crates/ruff_python_ast/src/source_code/mod.rs @@ -15,7 +15,7 @@ use rustpython_parser::{lexer, Mode, ParseError}; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Formatter}; use std::sync::Arc; -pub use stylist::Stylist; +pub use stylist::{Quote, Stylist}; /// Run round-trip source code generation on a given Python code. pub fn round_trip(code: &str, source_path: &str) -> Result { diff --git a/crates/ruff_python_semantic/src/context.rs b/crates/ruff_python_semantic/src/context.rs index d57bb385e1e00a..eb52f550ff3cd7 100644 --- a/crates/ruff_python_semantic/src/context.rs +++ b/crates/ruff_python_semantic/src/context.rs @@ -336,6 +336,11 @@ impl<'a> Context<'a> { Some(self.stmts[parent_id]) } + /// Return the current `Expr`. + pub fn expr(&self) -> Option<&'a Expr> { + self.exprs.iter().last().copied() + } + /// Return the parent `Expr` of the current `Expr`. pub fn expr_parent(&self) -> Option<&'a Expr> { self.exprs.iter().rev().nth(1).copied()