mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-15 16:10:38 +00:00
Replace dynamic implicit concatenation detection with parser flag (#6513)
## Summary In https://github.com/astral-sh/ruff/pull/6512, we added a flag to the AST to mark implicitly-concatenated string expressions. This PR makes use of that flag to remove the `is_implicit_concatenation` method. ## Test Plan `cargo test`
This commit is contained in:
parent
40407dcce5
commit
a7cf8f0b77
8 changed files with 52 additions and 150 deletions
|
@ -1,13 +1,13 @@
|
|||
use std::iter;
|
||||
|
||||
use ruff_python_ast::{
|
||||
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
|
||||
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, StringConstant,
|
||||
UnaryOp,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use ruff_formatter::{format_args, write, FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||
use ruff_python_ast::str::is_implicit_concatenation;
|
||||
|
||||
use crate::comments::{trailing_comments, trailing_node_comments};
|
||||
use crate::expression::expr_constant::ExprConstantLayout;
|
||||
|
@ -157,8 +157,11 @@ impl FormatExprBinOp {
|
|||
fn layout<'a>(bin_op: &'a ExprBinOp, context: &PyFormatContext) -> BinOpLayout<'a> {
|
||||
if let Some(
|
||||
constant @ ExprConstant {
|
||||
value: Constant::Str(_),
|
||||
range,
|
||||
value:
|
||||
Constant::Str(StringConstant {
|
||||
implicit_concatenated: true,
|
||||
..
|
||||
}),
|
||||
..
|
||||
},
|
||||
) = bin_op.left.as_constant_expr()
|
||||
|
@ -169,7 +172,6 @@ impl FormatExprBinOp {
|
|||
&& context.node_level().is_parenthesized()
|
||||
&& !comments.has_dangling_comments(constant)
|
||||
&& !comments.has_dangling_comments(bin_op)
|
||||
&& is_implicit_concatenation(&context.source()[*range])
|
||||
{
|
||||
BinOpLayout::LeftString(constant)
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use ruff_python_ast::{Constant, ExprConstant, Ranged};
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
|
||||
use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::str::is_implicit_concatenation;
|
||||
use ruff_python_ast::{Constant, ExprConstant, Ranged};
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
|
||||
use crate::expression::number::{FormatComplex, FormatFloat, FormatInt};
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
|
@ -80,10 +78,9 @@ impl NeedsParentheses for ExprConstant {
|
|||
_parent: AnyNodeRef,
|
||||
context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
if self.value.is_str() || self.value.is_bytes() {
|
||||
let contents = context.locator().slice(self.range());
|
||||
if self.value.is_implicit_concatenated() {
|
||||
// Don't wrap triple quoted strings
|
||||
if is_multiline_string(self, context.source()) || !is_implicit_concatenation(contents) {
|
||||
if is_multiline_string(self, context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
OptionalParentheses::Multiline
|
||||
|
|
|
@ -4,7 +4,6 @@ use bitflags::bitflags;
|
|||
|
||||
use ruff_formatter::{format_args, write, FormatError};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::str::is_implicit_concatenation;
|
||||
use ruff_python_ast::{self as ast, ExprConstant, ExprFString, Ranged};
|
||||
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
||||
use ruff_python_parser::{Mode, Tok};
|
||||
|
@ -49,6 +48,17 @@ impl<'a> AnyString<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the string is implicitly concatenated.
|
||||
fn implicit_concatenated(&self) -> bool {
|
||||
match self {
|
||||
Self::Constant(ExprConstant { value, .. }) => value.is_implicit_concatenated(),
|
||||
Self::FString(ExprFString {
|
||||
implicit_concatenated,
|
||||
..
|
||||
}) => *implicit_concatenated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for AnyString<'_> {
|
||||
|
@ -103,14 +113,11 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
|||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
match self.layout {
|
||||
StringLayout::Default => {
|
||||
let string_range = self.string.range();
|
||||
let string_content = f.context().locator().slice(string_range);
|
||||
|
||||
if is_implicit_concatenation(string_content) {
|
||||
if self.string.implicit_concatenated() {
|
||||
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
||||
} else {
|
||||
FormatStringPart::new(
|
||||
string_range,
|
||||
self.string.range(),
|
||||
self.string.quoting(&f.context().locator()),
|
||||
&f.context().locator(),
|
||||
f.options().quote_style(),
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::str::is_implicit_concatenation;
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{Constant, ExprConstant};
|
||||
use ruff_python_trivia::{lines_after_ignoring_trivia, lines_before};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::comments::{leading_comments, trailing_comments};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
|
@ -78,7 +76,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
write!(f, [first.format()])?;
|
||||
}
|
||||
SuiteKind::Function => {
|
||||
if let Some(constant) = get_docstring(first, &f.context().locator()) {
|
||||
if let Some(constant) = get_docstring(first) {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
|
@ -95,7 +93,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
}
|
||||
}
|
||||
SuiteKind::Class => {
|
||||
if let Some(constant) = get_docstring(first, &f.context().locator()) {
|
||||
if let Some(constant) = get_docstring(first) {
|
||||
if !comments.has_leading_comments(first)
|
||||
&& lines_before(first.start(), source) > 1
|
||||
{
|
||||
|
@ -257,26 +255,20 @@ const fn is_import_definition(stmt: &Stmt) -> bool {
|
|||
}
|
||||
|
||||
/// Checks if the statement is a simple string that can be formatted as a docstring
|
||||
fn get_docstring<'a>(stmt: &'a Stmt, locator: &Locator) -> Option<&'a ExprConstant> {
|
||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Expr::Constant(constant) = value.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if let ExprConstant {
|
||||
value: Constant::Str(..),
|
||||
range,
|
||||
..
|
||||
} = constant
|
||||
{
|
||||
if is_implicit_concatenation(locator.slice(*range)) {
|
||||
return None;
|
||||
}
|
||||
return Some(constant);
|
||||
fn get_docstring(stmt: &Stmt) -> Option<&ExprConstant> {
|
||||
let stmt_expr = stmt.as_expr_stmt()?;
|
||||
let expr_constant = stmt_expr.value.as_constant_expr()?;
|
||||
if matches!(
|
||||
expr_constant.value,
|
||||
Constant::Str(ast::StringConstant {
|
||||
implicit_concatenated: false,
|
||||
..
|
||||
})
|
||||
) {
|
||||
Some(expr_constant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue