Invert quote-style when generating code within f-strings (#4487)

This commit is contained in:
Charlie Marsh 2023-05-18 10:33:33 -04:00 committed by GitHub
parent 2fb312bb2b
commit 73efbeb581
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 271 additions and 162 deletions

View file

@ -25,3 +25,4 @@ bytes()
bytes(b"foo") bytes(b"foo")
bytes(b""" bytes(b"""
foo""") foo""")
f"{str()}"

View file

@ -43,3 +43,6 @@ second = first + [
[] + foo + [ # This will be preserved, but doesn't prevent the fix [] + foo + [ # This will be preserved, but doesn't prevent the fix
] ]
# Uses the non-preferred quote style, which should be retained.
f"{[*a(), 'b']}"

View file

@ -13,7 +13,8 @@ use rustpython_parser::ast::{
use ruff_diagnostics::{Diagnostic, Fix}; use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::all::{extract_all_names, AllNamesFlags}; use ruff_python_ast::all::{extract_all_names, AllNamesFlags};
use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path}; 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::types::{Node, RefEquality};
use ruff_python_ast::typing::{parse_type_annotation, AnnotationKind}; use ruff_python_ast::typing::{parse_type_annotation, AnnotationKind};
use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor}; use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor};
@ -134,6 +135,33 @@ impl<'a> Checker<'a> {
} }
noqa::rule_is_ignored(code, offset, self.noqa_line_for, self.locator) 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<Quote> {
if !context.in_f_string() {
return None;
}
// Find the quote character used to start the containing f-string.
let expr = context.expr()?;
let string_range = indexer.f_string_range(expr.start())?;
let trailing_quote = trailing_quote(locator.slice(string_range))?;
// 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> impl<'a, 'b> Visitor<'b> for Checker<'a>

View file

@ -62,14 +62,14 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
}) => { }) => {
let Some(parent) = checker.ctx.expr_parent() else { let Some(parent) = checker.ctx.expr_parent() else {
if any_over_expr(expr, &has_string_literal) { if any_over_expr(expr, &has_string_literal) {
return Some(unparse_expr(expr, checker.stylist)); return Some(unparse_expr(expr, checker.generator()));
} }
return None; return None;
}; };
// Only evaluate the full BinOp, not the nested components. // Only evaluate the full BinOp, not the nested components.
let Expr::BinOp(_ )= parent else { let Expr::BinOp(_ )= parent else {
if any_over_expr(expr, &has_string_literal) { if any_over_expr(expr, &has_string_literal) {
return Some(unparse_expr(expr, checker.stylist)); return Some(unparse_expr(expr, checker.generator()));
} }
return None; return None;
}; };
@ -81,12 +81,12 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
}; };
// "select * from table where val = {}".format(...) // "select * from table where val = {}".format(...)
if attr == "format" && string_literal(value).is_some() { if attr == "format" && string_literal(value).is_some() {
return Some(unparse_expr(expr, checker.stylist)); return Some(unparse_expr(expr, checker.generator()));
}; };
None None
} }
// f"select * from table where val = {val}" // f"select * from table where val = {val}"
Expr::JoinedStr(_) => Some(unparse_expr(expr, checker.stylist)), Expr::JoinedStr(_) => Some(unparse_expr(expr, checker.generator())),
_ => None, _ => None,
} }
} }

View file

@ -48,7 +48,7 @@ pub(crate) fn request_without_timeout(
Expr::Constant(ast::ExprConstant { Expr::Constant(ast::ExprConstant {
value: value @ Constant::None, value: value @ Constant::None,
.. ..
}) => Some(unparse_constant(value, checker.stylist)), }) => Some(unparse_constant(value, checker.generator())),
_ => None, _ => None,
} { } {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -56,7 +56,7 @@ pub(crate) fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg:
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_stmt(&assertion_error(msg), checker.stylist), unparse_stmt(&assertion_error(msg), checker.generator()),
stmt.range(), stmt.range(),
))); )));
} }

View file

@ -97,9 +97,9 @@ fn duplicate_handler_exceptions<'a>(
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
if unique_elts.len() == 1 { if unique_elts.len() == 1 {
unparse_expr(unique_elts[0], checker.stylist) unparse_expr(unique_elts[0], checker.generator())
} else { } else {
unparse_expr(&type_pattern(unique_elts), checker.stylist) unparse_expr(&type_pattern(unique_elts), checker.generator())
}, },
expr.range(), expr.range(),
))); )));

View file

@ -69,7 +69,7 @@ pub(crate) fn getattr_with_constant(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&attribute(obj, value), checker.stylist), unparse_expr(&attribute(obj, value), checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -45,14 +45,14 @@ pub(crate) fn redundant_tuple_in_exception_handler(
}; };
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
RedundantTupleInExceptionHandler { RedundantTupleInExceptionHandler {
name: unparse_expr(elt, checker.stylist), name: unparse_expr(elt, checker.generator()),
}, },
type_.range(), type_.range(),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(elt, checker.stylist), unparse_expr(elt, checker.generator()),
type_.range(), type_.range(),
))); )));
} }

View file

@ -4,7 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged, Stmt};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_stmt; 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 ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private};
use crate::checkers::ast::Checker; 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 { let stmt = Stmt::Assign(ast::StmtAssign {
targets: vec![Expr::Attribute(ast::ExprAttribute { targets: vec![Expr::Attribute(ast::ExprAttribute {
value: Box::new(obj.clone()), value: Box::new(obj.clone()),
@ -39,7 +39,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, stylist: &Stylist) -> String
type_comment: None, type_comment: None,
range: TextRange::default(), range: TextRange::default(),
}); });
unparse_stmt(&stmt, stylist) unparse_stmt(&stmt, generator)
} }
/// B010 /// B010
@ -84,7 +84,7 @@ pub(crate) fn setattr_with_constant(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
assignment(obj, name, value, checker.stylist), assignment(obj, name, value, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -4,7 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged, Stmt};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_stmt; 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 ruff_python_ast::whitespace;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -183,7 +183,13 @@ impl Violation for DotFormatInException {
/// 1. Insert the exception argument into a variable assignment before the /// 1. Insert the exception argument into a variable assignment before the
/// `raise` statement. The variable name is `msg`. /// `raise` statement. The variable name is `msg`.
/// 2. Replace the exception argument with the variable name. /// 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 { let node = Expr::Name(ast::ExprName {
id: "msg".into(), id: "msg".into(),
ctx: ExprContext::Store, ctx: ExprContext::Store,
@ -195,7 +201,7 @@ fn generate_fix(stylist: &Stylist, stmt: &Stmt, exc_arg: &Expr, indentation: &st
type_comment: None, type_comment: None,
range: TextRange::default(), range: TextRange::default(),
}); });
let assignment = unparse_stmt(&node1, stylist); let assignment = unparse_stmt(&node1, generator);
#[allow(deprecated)] #[allow(deprecated)]
Fix::unspecified_edits( Fix::unspecified_edits(
Edit::insertion( 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 let Some(indentation) = indentation {
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(generate_fix( diagnostic.set_fix(generate_fix(
checker.stylist,
stmt, stmt,
first, first,
indentation, 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 let Some(indentation) = indentation {
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(generate_fix( diagnostic.set_fix(generate_fix(
checker.stylist,
stmt, stmt,
first, first,
indentation, 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 let Some(indentation) = indentation {
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(generate_fix( diagnostic.set_fix(generate_fix(
checker.stylist,
stmt, stmt,
first, first,
indentation, indentation,
checker.stylist,
checker.generator(),
)); ));
} }
} }

View file

@ -443,7 +443,7 @@ pub(crate) fn non_unique_enums<'a, 'b>(
if !seen_targets.insert(ComparableExpr::from(value)) { if !seen_targets.insert(ComparableExpr::from(value)) {
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
NonUniqueEnums { NonUniqueEnums {
value: unparse_expr(value, checker.stylist), value: unparse_expr(value, checker.generator()),
}, },
stmt.range(), stmt.range(),
); );
@ -612,7 +612,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
let bool_op = node; let bool_op = node;
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&bool_op, checker.stylist), unparse_expr(&bool_op, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -61,7 +61,7 @@ fn traverse_union<'a>(
if !seen_nodes.insert(expr.into()) { if !seen_nodes.insert(expr.into()) {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
DuplicateUnionMember { DuplicateUnionMember {
duplicate_name: unparse_expr(expr, checker.stylist), duplicate_name: unparse_expr(expr, checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -82,7 +82,7 @@ fn traverse_union<'a>(
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr( unparse_expr(
if expr == left.as_ref() { right } else { left }, if expr == left.as_ref() { right } else { left },
checker.stylist, checker.generator(),
), ),
parent.range(), parent.range(),
))); )));

View file

@ -198,7 +198,7 @@ pub(crate) fn unittest_assertion(
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) { if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_stmt(&stmt, checker.stylist), unparse_stmt(&stmt, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -78,7 +78,7 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
kind: None, kind: None,
range: TextRange::default(), 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`. /// 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)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
format!("({})", unparse_expr(&node, checker.stylist,)), format!("({})", unparse_expr(&node, checker.generator())),
name_range, name_range,
))); )));
} }
@ -195,7 +195,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
}); });
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node, checker.stylist), unparse_expr(&node, checker.generator()),
name_range, name_range,
))); )));
} }
@ -228,7 +228,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
}); });
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node, checker.stylist), unparse_expr(&node, checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -278,7 +278,7 @@ fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) {
}); });
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
format!("({})", unparse_expr(&node, checker.stylist,)), format!("({})", unparse_expr(&node, checker.generator())),
expr.range(), expr.range(),
))); )));
} }
@ -373,7 +373,7 @@ fn handle_single_name(checker: &mut Checker, expr: &Expr, value: &Expr) {
let node = value.clone(); let node = value.clone();
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node, checker.stylist), unparse_expr(&node, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -11,8 +11,6 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, AutofixKind, Diagnostic, Edit
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::{contains_effect, has_comments, unparse_expr, Truthiness}; 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::checkers::ast::Checker;
use crate::registry::AsRule; 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. // multiple duplicates, the fixes will conflict.
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&bool_op, checker.stylist), unparse_expr(&bool_op, checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -457,7 +455,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
let in_expr = node2.into(); let in_expr = node2.into();
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
CompareWithTuple { CompareWithTuple {
replacement: unparse_expr(&in_expr, checker.stylist), replacement: unparse_expr(&in_expr, checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -481,7 +479,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&in_expr, checker.stylist), unparse_expr(&in_expr, checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -604,7 +602,7 @@ pub(crate) fn get_short_circuit_edit(
range: TextRange, range: TextRange,
truthiness: Truthiness, truthiness: Truthiness,
in_boolean_test: bool, in_boolean_test: bool,
stylist: &Stylist, checker: &Checker,
) -> Edit { ) -> Edit {
let content = if in_boolean_test { let content = if in_boolean_test {
match truthiness { match truthiness {
@ -615,7 +613,7 @@ pub(crate) fn get_short_circuit_edit(
} }
} }
} else { } else {
unparse_expr(expr, stylist) unparse_expr(expr, checker.generator())
}; };
Edit::range_replacement(content, range) Edit::range_replacement(content, range)
} }
@ -623,8 +621,7 @@ pub(crate) fn get_short_circuit_edit(
fn is_short_circuit( fn is_short_circuit(
expr: &Expr, expr: &Expr,
expected_op: Boolop, expected_op: Boolop,
context: &Context, checker: &Checker,
stylist: &Stylist,
) -> Option<(Edit, ContentAround)> { ) -> Option<(Edit, ContentAround)> {
let Expr::BoolOp(ast::ExprBoolOp { op, values, range: _, }) = expr else { let Expr::BoolOp(ast::ExprBoolOp { op, values, range: _, }) = expr else {
return None; return None;
@ -643,12 +640,14 @@ fn is_short_circuit(
for (index, (value, next_value)) in values.iter().tuple_windows().enumerate() { for (index, (value, next_value)) in values.iter().tuple_windows().enumerate() {
// Keep track of the location of the furthest-right, truthy or falsey expression. // 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 value_truthiness = Truthiness::from_expr(value, |id| checker.ctx.is_builtin(id));
let next_value_truthiness = Truthiness::from_expr(next_value, |id| context.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. // Keep track of the location of the furthest-right, non-effectful expression.
if value_truthiness.is_unknown() 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(); location = next_value.start();
continue; continue;
@ -668,8 +667,8 @@ fn is_short_circuit(
value, value,
TextRange::new(location, expr.end()), TextRange::new(location, expr.end()),
short_circuit_truthiness, short_circuit_truthiness,
context.in_boolean_test(), checker.ctx.in_boolean_test(),
stylist, checker,
)); ));
break; break;
} }
@ -686,8 +685,8 @@ fn is_short_circuit(
next_value, next_value,
TextRange::new(location, expr.end()), TextRange::new(location, expr.end()),
short_circuit_truthiness, short_circuit_truthiness,
context.in_boolean_test(), checker.ctx.in_boolean_test(),
stylist, checker,
)); ));
break; break;
} }
@ -701,8 +700,7 @@ fn is_short_circuit(
/// SIM222 /// SIM222
pub(crate) fn expr_or_true(checker: &mut Checker, expr: &Expr) { 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( let mut diagnostic = Diagnostic::new(
ExprOrTrue { ExprOrTrue {
expr: edit.content().unwrap_or_default().to_string(), expr: edit.content().unwrap_or_default().to_string(),
@ -720,8 +718,7 @@ pub(crate) fn expr_or_true(checker: &mut Checker, expr: &Expr) {
/// SIM223 /// SIM223
pub(crate) fn expr_and_false(checker: &mut Checker, expr: &Expr) { 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( let mut diagnostic = Diagnostic::new(
ExprAndFalse { ExprAndFalse {
expr: edit.content().unwrap_or_default().to_string(), expr: edit.content().unwrap_or_default().to_string(),

View file

@ -149,7 +149,7 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
let new_env_var = node.into(); let new_env_var = node.into();
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&new_env_var, checker.stylist), unparse_expr(&new_env_var, checker.generator()),
slice.range(), slice.range(),
))); )));
} }

View file

@ -348,7 +348,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
return; return;
} }
let condition = unparse_expr(test, checker.stylist); let condition = unparse_expr(test, checker.generator());
let fixable = matches!(if_return, Bool::True) let fixable = matches!(if_return, Bool::True)
&& matches!(else_return, Bool::False) && matches!(else_return, Bool::False)
&& !has_comments(stmt, checker.locator) && !has_comments(stmt, checker.locator)
@ -364,7 +364,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_stmt(&node.into(), checker.stylist), unparse_stmt(&node.into(), checker.generator()),
stmt.range(), stmt.range(),
))); )));
} else { } else {
@ -387,7 +387,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_stmt(&node2.into(), checker.stylist), unparse_stmt(&node2.into(), checker.generator()),
stmt.range(), 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 target_var = &body_targets[0];
let ternary = ternary(target_var, body_value, test, orelse_value); 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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());
@ -859,7 +859,7 @@ pub(crate) fn use_dict_get_with_default(
type_comment: None, type_comment: None,
range: TextRange::default(), 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. // Don't flag if the resulting expression would exceed the maximum line length.
let line_start = checker.locator.line_start(stmt.start()); let line_start = checker.locator.line_start(stmt.start());

View file

@ -97,7 +97,7 @@ pub(crate) fn explicit_true_false_in_ifexpr(
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
IfExprWithTrueFalse { IfExprWithTrueFalse {
expr: unparse_expr(test, checker.stylist), expr: unparse_expr(test, checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -105,7 +105,7 @@ pub(crate) fn explicit_true_false_in_ifexpr(
if matches!(test, Expr::Compare(_)) { if matches!(test, Expr::Compare(_)) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&test.clone(), checker.stylist), unparse_expr(&test.clone(), checker.generator()),
expr.range(), expr.range(),
))); )));
} else if checker.ctx.is_builtin("bool") { } else if checker.ctx.is_builtin("bool") {
@ -122,7 +122,7 @@ pub(crate) fn explicit_true_false_in_ifexpr(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node1.into(), checker.stylist), unparse_expr(&node1.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
}; };
@ -153,7 +153,7 @@ pub(crate) fn explicit_false_true_in_ifexpr(
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
IfExprWithFalseTrue { IfExprWithFalseTrue {
expr: unparse_expr(test, checker.stylist), expr: unparse_expr(test, checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -166,7 +166,7 @@ pub(crate) fn explicit_false_true_in_ifexpr(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node1.into(), checker.stylist), unparse_expr(&node1.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -201,8 +201,8 @@ pub(crate) fn twisted_arms_in_ifexpr(
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
IfExprWithTwistedArms { IfExprWithTwistedArms {
expr_body: unparse_expr(body, checker.stylist), expr_body: unparse_expr(body, checker.generator()),
expr_else: unparse_expr(orelse, checker.stylist), expr_else: unparse_expr(orelse, checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -218,7 +218,7 @@ pub(crate) fn twisted_arms_in_ifexpr(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node3.into(), checker.stylist), unparse_expr(&node3.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -107,8 +107,8 @@ pub(crate) fn negation_with_equal_op(
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
NegateEqualOp { NegateEqualOp {
left: unparse_expr(left, checker.stylist), left: unparse_expr(left, checker.generator()),
right: unparse_expr(&comparators[0], checker.stylist), right: unparse_expr(&comparators[0], checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -121,7 +121,7 @@ pub(crate) fn negation_with_equal_op(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node.into(), checker.stylist), unparse_expr(&node.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -157,8 +157,8 @@ pub(crate) fn negation_with_not_equal_op(
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
NegateNotEqualOp { NegateNotEqualOp {
left: unparse_expr(left, checker.stylist), left: unparse_expr(left, checker.generator()),
right: unparse_expr(&comparators[0], checker.stylist), right: unparse_expr(&comparators[0], checker.generator()),
}, },
expr.range(), expr.range(),
); );
@ -171,7 +171,7 @@ pub(crate) fn negation_with_not_equal_op(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node.into(), checker.stylist), unparse_expr(&node.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -192,7 +192,7 @@ pub(crate) fn double_negation(checker: &mut Checker, expr: &Expr, op: Unaryop, o
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
DoubleNegation { DoubleNegation {
expr: unparse_expr(operand, checker.stylist), expr: unparse_expr(operand, checker.generator()),
}, },
expr.range(), 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() { if checker.ctx.in_boolean_test() {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(operand, checker.stylist), unparse_expr(operand, checker.generator()),
expr.range(), expr.range(),
))); )));
} else if checker.ctx.is_builtin("bool") { } 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)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&node1.into(), checker.stylist), unparse_expr(&node1.into(), checker.generator()),
expr.range(), expr.range(),
))); )));
}; };

View file

@ -7,7 +7,7 @@ use unicode_width::UnicodeWidthStr;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_stmt; 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::checkers::ast::Checker;
use crate::registry::{AsRule, Rule}; use crate::registry::{AsRule, Rule};
@ -171,7 +171,7 @@ fn return_values_for_siblings<'a>(stmt: &'a Stmt, sibling: &'a Stmt) -> Option<L
} }
/// Generate a return statement for an `any` or `all` builtin comprehension. /// Generate a return statement for an `any` or `all` builtin comprehension.
fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, stylist: &Stylist) -> String { fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Generator) -> String {
let node = ast::ExprGeneratorExp { let node = ast::ExprGeneratorExp {
elt: Box::new(test.clone()), elt: Box::new(test.clone()),
generators: vec![Comprehension { 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())), value: Some(Box::new(node2.into())),
range: TextRange::default(), range: TextRange::default(),
}; };
unparse_stmt(&node3.into(), stylist) unparse_stmt(&node3.into(), generator)
} }
/// SIM110, SIM111 /// SIM110, SIM111
@ -220,7 +220,7 @@ pub(crate) fn convert_for_loop_to_any_all(
loop_info.test, loop_info.test,
loop_info.target, loop_info.target,
loop_info.iter, loop_info.iter,
checker.stylist, checker.generator(),
); );
// Don't flag if the resulting expression would exceed the maximum line length. // 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, &test,
loop_info.target, loop_info.target,
loop_info.iter, loop_info.iter,
checker.stylist, checker.generator(),
); );
// Don't flag if the resulting expression would exceed the maximum line length. // Don't flag if the resulting expression would exceed the maximum line length.

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation, CacheKey}; use ruff_macros::{derive_message_formats, violation, CacheKey};
use ruff_python_ast::helpers::{resolve_imported_module_path, unparse_stmt}; 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 ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -90,7 +90,7 @@ fn fix_banned_relative_import(
level: Option<u32>, level: Option<u32>,
module: Option<&str>, module: Option<&str>,
module_path: Option<&[String]>, module_path: Option<&[String]>,
stylist: &Stylist, generator: Generator,
) -> Option<Fix> { ) -> Option<Fix> {
// Only fix is the module path is known. // Only fix is the module path is known.
let Some(module_path) = resolve_imported_module_path(level, module, module_path) else { 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)), level: Some(Int::new(0)),
range: TextRange::default(), range: TextRange::default(),
}; };
let content = unparse_stmt(&node.into(), stylist); let content = unparse_stmt(&node.into(), generator);
#[allow(deprecated)] #[allow(deprecated)]
Some(Fix::unspecified(Edit::range_replacement( Some(Fix::unspecified(Edit::range_replacement(
content, content,
@ -142,7 +142,7 @@ pub fn banned_relative_import(
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
if let Some(fix) = 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); diagnostic.set_fix(fix);
}; };

View file

@ -80,7 +80,7 @@ pub(crate) fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner:
// convertible to f-string parts). // convertible to f-string parts).
let Some(new_expr) = build_fstring(joiner, joinees) else { return }; 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( let mut diagnostic = Diagnostic::new(
StaticJoinToFString { StaticJoinToFString {

View file

@ -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 ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::{self, Cmpop, Expr}; use rustpython_parser::ast::{self, Cmpop, Expr};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; 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 { pub(crate) fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O" name == "l" || name == "I" || name == "O"
} }
@ -13,7 +14,7 @@ pub(crate) fn compare(
left: &Expr, left: &Expr,
ops: &[Cmpop], ops: &[Cmpop],
comparators: &[Expr], comparators: &[Expr],
stylist: &Stylist, generator: Generator,
) -> String { ) -> String {
let node = ast::ExprCompare { let node = ast::ExprCompare {
left: Box::new(left.clone()), left: Box::new(left.clone()),
@ -21,7 +22,7 @@ pub(crate) fn compare(
comparators: comparators.to_vec(), comparators: comparators.to_vec(),
range: TextRange::default(), range: TextRange::default(),
}; };
unparse_expr(&node.into(), stylist) unparse_expr(&node.into(), generator)
} }
pub(super) fn is_overlong( pub(super) fn is_overlong(

View file

@ -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_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, 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::helpers::{has_leading_content, has_trailing_content, unparse_stmt};
use ruff_python_ast::newlines::StrExt; 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_ast::whitespace::leading_space;
use ruff_python_semantic::context::Context; use ruff_python_semantic::context::Context;
use ruff_python_semantic::scope::ScopeKind; 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::checkers::ast::Checker;
use crate::registry::AsRule; use crate::registry::AsRule;
@ -88,10 +89,16 @@ pub(crate) fn lambda_assignment(
let first_line = checker.locator.line(stmt.start()); let first_line = checker.locator.line(stmt.start());
let indentation = &leading_space(first_line); let indentation = &leading_space(first_line);
let mut indented = String::new(); let mut indented = String::new();
for (idx, line) in for (idx, line) in function(
function(&checker.ctx, id, args, body, annotation, checker.stylist) &checker.ctx,
.universal_newlines() id,
.enumerate() args,
body,
annotation,
checker.generator(),
)
.universal_newlines()
.enumerate()
{ {
if idx == 0 { if idx == 0 {
indented.push_str(&line); indented.push_str(&line);
@ -158,7 +165,7 @@ fn function(
args: &Arguments, args: &Arguments,
body: &Expr, body: &Expr,
annotation: Option<&Expr>, annotation: Option<&Expr>,
stylist: &Stylist, generator: Generator,
) -> String { ) -> String {
let body = Stmt::Return(ast::StmtReturn { let body = Stmt::Return(ast::StmtReturn {
value: Some(Box::new(body.clone())), value: Some(Box::new(body.clone())),
@ -203,7 +210,7 @@ fn function(
type_comment: None, type_comment: None,
range: TextRange::default(), range: TextRange::default(),
}); });
return unparse_stmt(&func, stylist); return unparse_stmt(&func, generator);
} }
} }
let func = Stmt::FunctionDef(ast::StmtFunctionDef { let func = Stmt::FunctionDef(ast::StmtFunctionDef {
@ -215,5 +222,5 @@ fn function(
type_comment: None, type_comment: None,
range: TextRange::default(), range: TextRange::default(),
}); });
unparse_stmt(&func, stylist) unparse_stmt(&func, generator)
} }

View file

@ -278,7 +278,7 @@ pub(crate) fn literal_comparisons(
.map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op)) .map(|(idx, op)| bad_ops.get(&idx).unwrap_or(op))
.copied() .copied()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let content = compare(left, &ops, comparators, checker.stylist); let content = compare(left, &ops, comparators, checker.generator());
for diagnostic in &mut diagnostics { for diagnostic in &mut diagnostics {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(

View file

@ -101,7 +101,12 @@ pub(crate) fn not_tests(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
compare(left, &[Cmpop::NotIn], comparators, checker.stylist), compare(
left,
&[Cmpop::NotIn],
comparators,
checker.generator(),
),
expr.range(), expr.range(),
))); )));
} }
@ -114,7 +119,12 @@ pub(crate) fn not_tests(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
compare(left, &[Cmpop::IsNot], comparators, checker.stylist), compare(
left,
&[Cmpop::IsNot],
comparators,
checker.generator(),
),
expr.range(), expr.range(),
))); )));
} }

View file

@ -106,7 +106,7 @@ pub(crate) fn repeated_keys(checker: &mut Checker, keys: &[Option<Expr>], values
let is_duplicate_value = seen_values.contains(&comparable_value); let is_duplicate_value = seen_values.contains(&comparable_value);
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
MultiValueRepeatedKeyLiteral { MultiValueRepeatedKeyLiteral {
name: unparse_expr(key, checker.stylist), name: unparse_expr(key, checker.generator()),
repeated_value: is_duplicate_value, repeated_value: is_duplicate_value,
}, },
key.range(), key.range(),

View file

@ -117,8 +117,8 @@ pub(crate) fn compare_to_empty_string(
if let Expr::Constant(ast::ExprConstant { value, .. }) = &lhs { if let Expr::Constant(ast::ExprConstant { value, .. }) = &lhs {
if let Constant::Str(s) = value { if let Constant::Str(s) = value {
if s.is_empty() { if s.is_empty() {
let constant = unparse_constant(value, checker.stylist); let constant = unparse_constant(value, checker.generator());
let expr = unparse_expr(rhs, checker.stylist); let expr = unparse_expr(rhs, checker.generator());
let existing = format!("{constant} {op} {expr}"); let existing = format!("{constant} {op} {expr}");
let replacement = format!("{}{expr}", op.into_unary()); let replacement = format!("{}{expr}", op.into_unary());
checker.diagnostics.push(Diagnostic::new( 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 Expr::Constant(ast::ExprConstant { value, .. }) = &rhs {
if let Constant::Str(s) = value { if let Constant::Str(s) = value {
if s.is_empty() { if s.is_empty() {
let expr = unparse_expr(lhs, checker.stylist); let expr = unparse_expr(lhs, checker.generator());
let constant = unparse_constant(value, checker.stylist); let constant = unparse_constant(value, checker.generator());
let existing = format!("{expr} {op} {constant}"); let existing = format!("{expr} {op} {constant}");
let replacement = format!("{}{expr}", op.into_unary()); let replacement = format!("{}{expr}", op.into_unary());
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -106,9 +106,9 @@ pub(crate) fn comparison_of_constant(
{ {
let diagnostic = Diagnostic::new( let diagnostic = Diagnostic::new(
ComparisonOfConstant { ComparisonOfConstant {
left_constant: unparse_constant(left_constant, checker.stylist), left_constant: unparse_constant(left_constant, checker.generator()),
op: op.into(), op: op.into(),
right_constant: unparse_constant(right_constant, checker.stylist), right_constant: unparse_constant(right_constant, checker.generator()),
}, },
left.range(), left.range(),
); );

View file

@ -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) { if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
MagicValueComparison { MagicValueComparison {
value: unparse_expr(comparison_expr, checker.stylist), value: unparse_expr(comparison_expr, checker.generator()),
}, },
comparison_expr.range(), comparison_expr.range(),
)); ));

View file

@ -67,7 +67,7 @@ pub(crate) fn manual_from_import(
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_stmt(&node.into(), checker.stylist), unparse_stmt(&node.into(), checker.generator()),
stmt.range(), stmt.range(),
))); )));
} }

View file

@ -149,7 +149,7 @@ pub(crate) fn nested_min_max(
}); });
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&flattened_expr, checker.stylist), unparse_expr(&flattened_expr, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -393,7 +393,7 @@ pub(crate) fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &N
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
RedefinedLoopName { 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, outer_kind: outer_assignment_target.binding_kind,
inner_kind: inner_assignment_target.binding_kind, inner_kind: inner_assignment_target.binding_kind,
}, },

View file

@ -12,7 +12,7 @@ use crate::checkers::ast::Checker;
#[violation] #[violation]
pub struct RepeatedIsinstanceCalls { pub struct RepeatedIsinstanceCalls {
obj: String, obj: String,
pub types: Vec<String>, types: Vec<String>,
} }
impl Violation for RepeatedIsinstanceCalls { impl Violation for RepeatedIsinstanceCalls {
@ -66,11 +66,11 @@ pub(crate) fn repeated_isinstance_calls(
if num_calls > 1 && types.len() > 1 { if num_calls > 1 && types.len() > 1 {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
RepeatedIsinstanceCalls { RepeatedIsinstanceCalls {
obj: unparse_expr(obj.as_expr(), checker.stylist), obj: unparse_expr(obj.as_expr(), checker.generator()),
types: types types: types
.iter() .iter()
.map(HashableExpr::as_expr) .map(HashableExpr::as_expr)
.map(|expr| unparse_expr(expr, checker.stylist)) .map(|expr| unparse_expr(expr, checker.generator()))
.sorted() .sorted()
.collect(), .collect(),
}, },

View file

@ -6,7 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged,
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_stmt; 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 ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -171,11 +171,14 @@ fn convert_to_class(
typename: &str, typename: &str,
body: Vec<Stmt>, body: Vec<Stmt>,
base_class: &Expr, base_class: &Expr,
stylist: &Stylist, generator: Generator,
) -> Fix { ) -> Fix {
#[allow(deprecated)] #[allow(deprecated)]
Fix::unspecified(Edit::range_replacement( 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(), stmt.range(),
)) ))
} }
@ -216,7 +219,7 @@ pub(crate) fn convert_named_tuple_functional_to_class(
typename, typename,
properties, properties,
base_class, base_class,
checker.stylist, checker.generator(),
)); ));
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -6,7 +6,7 @@ use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Keyword, Ranged,
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_stmt; 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 ruff_python_stdlib::identifiers::is_identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -222,13 +222,13 @@ fn convert_to_class(
body: Vec<Stmt>, body: Vec<Stmt>,
total_keyword: Option<&Keyword>, total_keyword: Option<&Keyword>,
base_class: &Expr, base_class: &Expr,
stylist: &Stylist, generator: Generator,
) -> Fix { ) -> Fix {
#[allow(deprecated)] #[allow(deprecated)]
Fix::unspecified(Edit::range_replacement( Fix::unspecified(Edit::range_replacement(
unparse_stmt( unparse_stmt(
&create_class_def_stmt(class_name, body, total_keyword, base_class), &create_class_def_stmt(class_name, body, total_keyword, base_class),
stylist, generator,
), ),
stmt.range(), stmt.range(),
)) ))
@ -270,7 +270,7 @@ pub(crate) fn convert_typed_dict_functional_to_class(
body, body,
total_keyword, total_keyword,
base_class, base_class,
checker.stylist, checker.generator(),
)); ));
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -51,7 +51,7 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(func, checker.stylist), unparse_expr(func, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -4,6 +4,7 @@ use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::unparse_constant;
use ruff_python_ast::str::is_implicit_concatenation; use ruff_python_ast::str::is_implicit_concatenation;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -64,20 +65,15 @@ pub(crate) fn native_literals(
LiteralType::Bytes LiteralType::Bytes
}}, expr.range()); }}, expr.range());
if checker.patch(diagnostic.kind.rule()) { 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)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
if id == "bytes" { content,
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
},
expr.range(), expr.range(),
))); )));
} }

View file

@ -117,7 +117,7 @@ fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) {
}; };
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
format!("({})", unparse_expr(&node.into(), checker.stylist,)), format!("({})", unparse_expr(&node.into(), checker.generator())),
target.range(), target.range(),
))); )));
} }

View file

@ -67,7 +67,7 @@ pub(crate) fn use_pep604_annotation(
if fixable && checker.patch(diagnostic.kind.rule()) { if fixable && checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&optional(slice), checker.stylist), unparse_expr(&optional(slice), checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -83,7 +83,7 @@ pub(crate) fn use_pep604_annotation(
Expr::Tuple(ast::ExprTuple { elts, .. }) => { Expr::Tuple(ast::ExprTuple { elts, .. }) => {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&union(elts), checker.stylist), unparse_expr(&union(elts), checker.generator()),
expr.range(), expr.range(),
))); )));
} }
@ -91,7 +91,7 @@ pub(crate) fn use_pep604_annotation(
// Single argument. // Single argument.
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(slice, checker.stylist), unparse_expr(slice, checker.generator()),
expr.range(), expr.range(),
))); )));
} }

View file

@ -1,6 +1,6 @@
use ruff_text_size::TextRange;
use std::fmt; use std::fmt;
use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Expr, Operator, Ranged}; use rustpython_parser::ast::{self, Expr, Operator, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
@ -94,7 +94,7 @@ pub(crate) fn use_pep604_isinstance(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
unparse_expr(&union(elts), checker.stylist), unparse_expr(&union(elts), checker.generator()),
types.range(), types.range(),
))); )));
} }

View file

@ -106,6 +106,7 @@ UP018.py:25:1: UP018 [*] Unnecessary call to `bytes`
25 |+b"foo" 25 |+b"foo"
26 26 | bytes(b""" 26 26 | bytes(b"""
27 27 | foo""") 27 27 | foo""")
28 28 | f"{str()}"
UP018.py:26:1: UP018 [*] Unnecessary call to `bytes` 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""" 28 | / bytes(b"""
29 | | foo""") 29 | | foo""")
| |_______^ UP018 | |_______^ UP018
30 | f"{str()}"
| |
= help: Replace with `bytes` = help: Replace with `bytes`
@ -125,5 +127,22 @@ UP018.py:26:1: UP018 [*] Unnecessary call to `bytes`
27 |-foo""") 27 |-foo""")
26 |+b""" 26 |+b"""
27 |+foo""" 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"{''}"

View file

@ -130,8 +130,8 @@ pub(crate) fn collection_literal_concatenation(checker: &mut Checker, expr: &Exp
let contents = match kind { let contents = match kind {
// Wrap the new expression in parentheses if it was a tuple // Wrap the new expression in parentheses if it was a tuple
Kind::Tuple => format!("({})", unparse_expr(&new_expr, checker.stylist)), Kind::Tuple => format!("({})", unparse_expr(&new_expr, checker.generator())),
Kind::List => unparse_expr(&new_expr, checker.stylist), Kind::List => unparse_expr(&new_expr, checker.generator()),
}; };
let fixable = !has_comments(expr, checker.locator); let fixable = !has_comments(expr, checker.locator);

View file

@ -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
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 | ] 45 45 | ]
46 46 |
47 47 | # Uses the non-preferred quote style, which should be retained.

View file

@ -17,26 +17,23 @@ use smallvec::SmallVec;
use crate::call_path::CallPath; use crate::call_path::CallPath;
use crate::newlines::UniversalNewlineIterator; 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}; use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
/// Generate source code from an [`Expr`]. /// Generate source code from an [`Expr`].
pub fn unparse_expr(expr: &Expr, stylist: &Stylist) -> String { pub fn unparse_expr(expr: &Expr, mut generator: Generator) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_expr(expr, 0); generator.unparse_expr(expr, 0);
generator.generate() generator.generate()
} }
/// Generate source code from a [`Stmt`]. /// Generate source code from a [`Stmt`].
pub fn unparse_stmt(stmt: &Stmt, stylist: &Stylist) -> String { pub fn unparse_stmt(stmt: &Stmt, mut generator: Generator) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_stmt(stmt); generator.unparse_stmt(stmt);
generator.generate() generator.generate()
} }
/// Generate source code from an [`Constant`]. /// Generate source code from an [`Constant`].
pub fn unparse_constant(constant: &Constant, stylist: &Stylist) -> String { pub fn unparse_constant(constant: &Constant, mut generator: Generator) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_constant(constant); generator.unparse_constant(constant);
generator.generate() generator.generate()
} }

View file

@ -4,7 +4,7 @@
use crate::source_code::Locator; use crate::source_code::Locator;
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::lexer::LexResult; use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok; use rustpython_parser::{StringKind, Tok};
pub struct Indexer { pub struct Indexer {
/// Stores the ranges of comments sorted by [`TextRange::start`] in increasing order. No two ranges are overlapping. /// 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 /// 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. /// [`TextRange::start`] position in increasing order. No two ranges are overlapping.
triple_quoted_string_ranges: Vec<TextRange>, triple_quoted_string_ranges: Vec<TextRange>,
/// 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<TextRange>,
} }
impl Indexer { impl Indexer {
pub fn from_tokens(tokens: &[LexResult], locator: &Locator) -> Self { pub fn from_tokens(tokens: &[LexResult], locator: &Locator) -> Self {
assert!(TextSize::try_from(locator.contents().len()).is_ok()); 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 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 // Token, end
let mut prev_end = TextSize::default(); let mut prev_end = TextSize::default();
let mut prev_token: Option<&Tok> = None; let mut prev_token: Option<&Tok> = None;
@ -59,7 +64,7 @@ impl Indexer {
match tok { match tok {
Tok::Comment(..) => { Tok::Comment(..) => {
commented_lines.push(*range); comment_ranges.push(*range);
} }
Tok::Newline | Tok::NonLogicalNewline => { Tok::Newline | Tok::NonLogicalNewline => {
line_start = range.end(); line_start = range.end();
@ -67,7 +72,15 @@ impl Indexer {
Tok::String { Tok::String {
triple_quoted: true, triple_quoted: true,
.. ..
} => string_ranges.push(*range), } => {
triple_quoted_string_ranges.push(*range);
}
Tok::String {
kind: StringKind::FString | StringKind::RawFString,
..
} => {
f_string_ranges.push(*range);
}
_ => {} _ => {}
} }
@ -75,9 +88,10 @@ impl Indexer {
prev_end = range.end(); prev_end = range.end();
} }
Self { Self {
comment_ranges: commented_lines, comment_ranges,
continuation_lines, continuation_lines,
triple_quoted_string_ranges: string_ranges, triple_quoted_string_ranges,
f_string_ranges,
} }
} }
@ -97,10 +111,27 @@ impl Indexer {
&self.triple_quoted_string_ranges &self.triple_quoted_string_ranges
} }
/// Returns `true` if the given offset is part of a continuation line.
pub fn is_continuation(&self, offset: TextSize, locator: &Locator) -> bool { pub fn is_continuation(&self, offset: TextSize, locator: &Locator) -> bool {
let line_start = locator.line_start(offset); let line_start = locator.line_start(offset);
self.continuation_lines.binary_search(&line_start).is_ok() self.continuation_lines.binary_search(&line_start).is_ok()
} }
/// Return the [`TextRange`] of the f-string containing a given offset.
pub fn f_string_range(&self, offset: TextSize) -> Option<TextRange> {
let Ok(string_range_index) = self.f_string_ranges.binary_search_by(|range| {
if offset < range.start() {
std::cmp::Ordering::Greater
} else if range.contains(offset) {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Less
}
}) else {
return None;
};
Some(self.f_string_ranges[string_range_index])
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -15,7 +15,7 @@ use rustpython_parser::{lexer, Mode, ParseError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
pub use stylist::Stylist; pub use stylist::{Quote, Stylist};
/// Run round-trip source code generation on a given Python code. /// Run round-trip source code generation on a given Python code.
pub fn round_trip(code: &str, source_path: &str) -> Result<String, ParseError> { pub fn round_trip(code: &str, source_path: &str) -> Result<String, ParseError> {

View file

@ -336,6 +336,11 @@ impl<'a> Context<'a> {
Some(self.stmts[parent_id]) 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`. /// Return the parent `Expr` of the current `Expr`.
pub fn expr_parent(&self) -> Option<&'a Expr> { pub fn expr_parent(&self) -> Option<&'a Expr> {
self.exprs.iter().rev().nth(1).copied() self.exprs.iter().rev().nth(1).copied()