Skip to content

Commit

Permalink
Format implicit string continuation
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Jun 23, 2023
1 parent c52aa8f commit cc6019d
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,51 @@
'''Multiline
String \"\"\"
'''

# String continuation

"Let's" "start" "with" "a" "simple" "example"

"Let's" "start" "with" "a" "simple" "example" "now repeat after me:" "I am confident" "I am confident" "I am confident" "I am confident" "I am confident"

(
"Let's" "start" "with" "a" "simple" "example" "now repeat after me:" "I am confident" "I am confident" "I am confident" "I am confident" "I am confident"
)

if (
a + "Let's"
"start"
"with"
"a"
"simple"
"example"
"now repeat after me:"
"I am confident"
"I am confident"
"I am confident"
"I am confident"
"I am confident"
):
pass

if "Let's" "start" "with" "a" "simple" "example" "now repeat after me:" "I am confident" "I am confident" "I am confident" "I am confident" "I am confident":
pass

(
# leading
"a" # trailing part commen

# leading part comment

"b" # trailing second part comment
# trailing
)

test_particular = [
# squares
'1.00000000100000000025',
'1.0000000000000000000000000100000000000000000000000' #...
'00025',
'1.0000000000000000000000000000000000000000000010000' #...
'0000000000000000000000000000000000000000025',
]
27 changes: 26 additions & 1 deletion crates/ruff_python_formatter/src/builders.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
use crate::context::NodeLevel;
use crate::prelude::*;
use crate::trivia::{lines_after, skip_trailing_trivia};
use ruff_formatter::write;
use ruff_formatter::{format_args, write, Argument, Arguments};
use ruff_text_size::TextSize;
use rustpython_parser::ast::Ranged;

/// Adds parentheses and indents `content` if it doesn't fit on a line.
pub(crate) fn optional_parentheses<'ast, T>(content: &T) -> OptionalParentheses<'_, 'ast>
where
T: Format<PyFormatContext<'ast>>,
{
OptionalParentheses {
inner: Argument::new(content),
}
}

pub(crate) struct OptionalParentheses<'a, 'ast> {
inner: Argument<'a, PyFormatContext<'ast>>,
}

impl<'ast> Format<PyFormatContext<'ast>> for OptionalParentheses<'_, 'ast> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
group(&format_args![
if_group_breaks(&text("(")),
soft_block_indent(&Arguments::from(&self.inner)),
if_group_breaks(&text(")"))
])
.fmt(f)
}
}

/// Provides Python specific extensions to [`Formatter`].
pub(crate) trait PyFormatterExtensions<'ast, 'buf> {
/// Creates a joiner that inserts the appropriate number of empty lines between two nodes, depending on the
Expand Down
35 changes: 23 additions & 12 deletions crates/ruff_python_formatter/src/expression/expr_constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ use crate::comments::Comments;
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::expression::string::FormatString;
use crate::expression::string::{FormatString, StringLayout};
use crate::prelude::*;
use crate::{not_yet_implemented_custom_text, verbatim_text, FormatNodeRule};
use ruff_formatter::write;
use ruff_formatter::{write, FormatRuleWithOptions};
use rustpython_parser::ast::{Constant, ExprConstant};

#[derive(Default)]
pub struct FormatExprConstant;
pub struct FormatExprConstant {
string_layout: StringLayout,
}

impl FormatRuleWithOptions<ExprConstant, PyFormatContext<'_>> for FormatExprConstant {
type Options = StringLayout;

fn with_options(mut self, options: Self::Options) -> Self {
self.string_layout = options;
self
}
}

impl FormatNodeRule<ExprConstant> for FormatExprConstant {
fn fmt_fields(&self, item: &ExprConstant, f: &mut PyFormatter) -> FormatResult<()> {
Expand All @@ -29,7 +40,7 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. } => {
write!(f, [verbatim_text(item)])
}
Constant::Str(_) => FormatString::new(item).fmt(f),
Constant::Str(_) => FormatString::new(item, self.string_layout).fmt(f),
Constant::Bytes(_) => {
not_yet_implemented_custom_text(r#"b"NOT_YET_IMPLEMENTED_BYTE_STRING""#).fmt(f)
}
Expand All @@ -44,14 +55,6 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
_node: &ExprConstant,
_f: &mut PyFormatter,
) -> FormatResult<()> {
// TODO(konstin): Reactivate when string formatting works, currently a source of unstable
// formatting, e.g.:
// magic_methods = (
// "enter exit "
// # we added divmod and rdivmod here instead of numerics
// # because there is no idivmod
// "divmod rdivmod neg pos abs invert "
// )
Ok(())
}
}
Expand All @@ -64,6 +67,14 @@ impl NeedsParentheses for ExprConstant {
comments: &Comments,
) -> Parentheses {
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional if self.value.is_str() && parenthesize.is_if_breaks() => {
// Custom handling that only adds parentheses for implicit concatenated strings.
if parenthesize.is_if_breaks() {
Parentheses::Custom
} else {
Parentheses::Optional
}
}
Parentheses::Optional => Parentheses::Never,
parentheses => parentheses,
}
Expand Down
18 changes: 5 additions & 13 deletions crates/ruff_python_formatter/src/expression/expr_tuple.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::builders::optional_parentheses;
use crate::comments::{dangling_node_comments, Comments};
use crate::context::PyFormatContext;
use crate::expression::parentheses::{
Expand Down Expand Up @@ -108,7 +109,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
block_indent(&ExprSequence::new(elts)),
&text(")"),
]
)?;
)
} else if is_parenthesized(*range, elts, f)
&& self.parentheses != TupleParentheses::StripInsideForLoop
{
Expand All @@ -125,20 +126,11 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
soft_block_indent(&ExprSequence::new(elts)),
&text(")"),
])]
)?;
)
} else {
write!(
f,
[group(&format_args![
// If there were previously no parentheses, add them only if the group breaks
if_group_breaks(&text("(")),
soft_block_indent(&ExprSequence::new(elts)),
if_group_breaks(&text(")")),
])]
)?;
// If there were previously no parentheses, add them only if the group breaks
optional_parentheses(&ExprSequence::new(elts)).fmt(f)
}

Ok(())
}

fn fmt_dangling_comments(&self, _node: &ExprTuple, _f: &mut PyFormatter) -> FormatResult<()> {
Expand Down
20 changes: 8 additions & 12 deletions crates/ruff_python_formatter/src/expression/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::builders::optional_parentheses;
use crate::comments::Comments;
use crate::context::NodeLevel;
use crate::expression::expr_tuple::TupleParentheses;
use crate::expression::parentheses::{NeedsParentheses, Parentheses, Parenthesize};
use crate::expression::string::StringLayout;
use crate::prelude::*;
use ruff_formatter::{
format_args, write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions,
Expand Down Expand Up @@ -37,7 +39,7 @@ pub(crate) mod expr_unary_op;
pub(crate) mod expr_yield;
pub(crate) mod expr_yield_from;
pub(crate) mod parentheses;
mod string;
pub(crate) mod string;

#[derive(Default)]
pub struct FormatExpr {
Expand Down Expand Up @@ -81,7 +83,10 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
Expr::Call(expr) => expr.format().fmt(f),
Expr::FormattedValue(expr) => expr.format().fmt(f),
Expr::JoinedStr(expr) => expr.format().fmt(f),
Expr::Constant(expr) => expr.format().fmt(f),
Expr::Constant(expr) => expr
.format()
.with_options(StringLayout::Default(Some(parentheses)))
.fmt(f),
Expr::Attribute(expr) => expr.format().fmt(f),
Expr::Subscript(expr) => expr.format().fmt(f),
Expr::Starred(expr) => expr.format().fmt(f),
Expand Down Expand Up @@ -109,16 +114,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
)
}
// Add optional parentheses. Ignore if the item renders parentheses itself.
Parentheses::Optional => {
write!(
f,
[group(&format_args![
if_group_breaks(&text("(")),
soft_block_indent(&format_expr),
if_group_breaks(&text(")"))
])]
)
}
Parentheses::Optional => optional_parentheses(&format_expr).fmt(f),
Parentheses::Custom | Parentheses::Never => Format::fmt(&format_expr, f),
};

Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_formatter/src/expression/parentheses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub enum Parentheses {
Never,
}

fn is_expression_parenthesized(expr: AnyNodeRef, contents: &str) -> bool {
pub(crate) fn is_expression_parenthesized(expr: AnyNodeRef, contents: &str) -> bool {
matches!(
first_non_trivia_token(expr.end(), contents),
Some(Token {
Expand Down
Loading

0 comments on commit cc6019d

Please sign in to comment.