Skip to content

Commit

Permalink
Require quotes to be on their own line
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Jan 8, 2024
1 parent 300fed8 commit b849250
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 116 deletions.
94 changes: 76 additions & 18 deletions crates/ruff_python_formatter/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::prelude::*;
use crate::preview::{
is_hug_parens_with_braces_and_square_brackets_enabled, is_multiline_string_handling_enabled,
};
use crate::string::{AnyString, StringPart};

mod binary_like;
pub(crate) mod expr_attribute;
Expand Down Expand Up @@ -1126,24 +1127,9 @@ pub(crate) fn is_expression_huggable(expr: &Expr, context: &PyFormatContext) ->

Expr::Starred(ast::ExprStarred { value, .. }) => is_expression_huggable(value, context),

Expr::StringLiteral(string) => {
is_multiline_string_handling_enabled(context)
&& !string.value.is_implicit_concatenated()
&& is_multiline_string(
LiteralExpressionRef::StringLiteral(string),
context.source(),
)
}
Expr::BytesLiteral(bytes) => {
is_multiline_string_handling_enabled(context)
&& !bytes.value.is_implicit_concatenated()
&& is_multiline_string(LiteralExpressionRef::BytesLiteral(bytes), context.source())
}
Expr::FString(fstring) => {
is_multiline_string_handling_enabled(context)
&& !fstring.value.is_implicit_concatenated()
&& is_multiline_fstring(fstring, context.source())
}
Expr::StringLiteral(string) => is_huggable_string(AnyString::String(string), context),
Expr::BytesLiteral(bytes) => is_huggable_string(AnyString::Bytes(bytes), context),
Expr::FString(fstring) => is_huggable_string(AnyString::FString(fstring), context),

Expr::BoolOp(_)
| Expr::NamedExpr(_)
Expand All @@ -1169,6 +1155,78 @@ pub(crate) fn is_expression_huggable(expr: &Expr, context: &PyFormatContext) ->
}
}

/// Returns `true` if `string` is a
/// * multiline string
/// * ...that is not implicitly concatenated
/// * ...where the opening and closing quotes have no content on the same line.
///
/// ## Examples
///
/// ```python
/// call("""
/// ABCD
/// """)
/// ```
///
/// Returns `true` because the `"""` are n their own line
///
/// ```python
/// call("""ABCD
/// More
/// """)
/// ```
///
/// Returns `false` because there's content on the same line as the opening quotes.
///
/// ```python
/// call("""
/// ABCD
/// More"""
/// )
/// ```
///
/// Returns `false` because there's content on the same line as the closing quotes.
///
/// ```python
/// call("""\
/// ABCD
/// More
/// """)
/// ```
///
/// Returns `true` because there's only a line continuation after the opening quotes.
fn is_huggable_string(string: AnyString, context: &PyFormatContext) -> bool {
if string.is_implicit_concatenated() || !is_multiline_string_handling_enabled(context) {
return false;
}

let multiline = match string {
AnyString::String(string) => is_multiline_string(
LiteralExpressionRef::StringLiteral(string),
context.source(),
),
AnyString::Bytes(bytes) => {
is_multiline_string(LiteralExpressionRef::BytesLiteral(bytes), context.source())
}
AnyString::FString(fstring) => is_multiline_fstring(fstring, context.source()),
};

if !multiline {
return false;
}

let locator = context.locator();
let part = StringPart::from_source(string.range(), &locator);
let source = part.source(&locator);

source
// allow `"""\`
.strip_prefix('\\')
.unwrap_or(source)
.starts_with(['\n', '\r'])
&& source.trim_end_matches(' ').ends_with('\n')
}

/// The precedence of [python operators](https://docs.python.org/3/reference/expressions.html#operator-precedence) from
/// highest to lowest priority.
///
Expand Down
6 changes: 5 additions & 1 deletion crates/ruff_python_formatter/src/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ impl StringPart {
configured_style
};

let raw_content = &locator.slice(self.content_range);
let raw_content = self.source(locator);

let quotes = match quoting {
Quoting::Preserve => self.quotes,
Expand All @@ -337,6 +337,10 @@ impl StringPart {
quotes,
}
}

pub(crate) fn source<'a>(&self, locator: &'a Locator) -> &'a str {
locator.slice(self.content_range)
}
}

#[derive(Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ this_will_also_become_one_line = ( # comment
```diff
--- Black
+++ Ruff
@@ -1,46 +1,69 @@
@@ -1,71 +1,104 @@
-"""cow
+(
+ """cow
Expand Down Expand Up @@ -271,8 +271,22 @@ this_will_also_become_one_line = ( # comment
+ ),
)
textwrap.dedent("""A one-line triple-quoted string.""")
textwrap.dedent("""A two-line triple-quoted string
@@ -54,18 +77,24 @@
-textwrap.dedent("""A two-line triple-quoted string
-since it goes to the next line.""")
-textwrap.dedent("""A three-line triple-quoted string
+textwrap.dedent(
+ """A two-line triple-quoted string
+since it goes to the next line."""
+)
+textwrap.dedent(
+ """A three-line triple-quoted string
that not only goes to the next line
-but also goes one line beyond.""")
+but also goes one line beyond."""
+)
textwrap.dedent("""\
A triple-quoted string
actually leveraging the textwrap.dedent functionality
that ends in a trailing newline,
representing e.g. file contents.
""")
Expand Down Expand Up @@ -301,7 +315,7 @@ this_will_also_become_one_line = ( # comment
# Another use case
data = yaml.load("""\
a: 1
@@ -85,11 +114,13 @@
@@ -85,11 +118,13 @@
MULTILINE = """
foo
""".replace("\n", "")
Expand All @@ -316,7 +330,7 @@ this_will_also_become_one_line = ( # comment
parser.usage += """
Custom extra help summary.
@@ -156,16 +187,24 @@
@@ -156,16 +191,24 @@
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
""" % (_C.__init__.__code__.co_firstlineno + 1,)
Expand Down Expand Up @@ -347,7 +361,24 @@ this_will_also_become_one_line = ( # comment
[
"""cow
moos""",
@@ -198,7 +237,7 @@
@@ -177,12 +220,14 @@
def dastardly_default_value(
- cow: String = json.loads("""this
+ cow: String = json.loads(
+ """this
is
quite
the
dastadardly
-value!"""),
+value!"""
+ ),
**kwargs,
):
pass
@@ -198,7 +243,7 @@
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
"""
Expand All @@ -356,7 +387,7 @@ this_will_also_become_one_line = ( # comment
this_will_stay_on_three_lines = (
"a" # comment
@@ -206,4 +245,6 @@
@@ -206,4 +251,6 @@
"c"
)
Expand Down Expand Up @@ -437,11 +468,15 @@ call(
),
)
textwrap.dedent("""A one-line triple-quoted string.""")
textwrap.dedent("""A two-line triple-quoted string
since it goes to the next line.""")
textwrap.dedent("""A three-line triple-quoted string
textwrap.dedent(
"""A two-line triple-quoted string
since it goes to the next line."""
)
textwrap.dedent(
"""A three-line triple-quoted string
that not only goes to the next line
but also goes one line beyond.""")
but also goes one line beyond."""
)
textwrap.dedent("""\
A triple-quoted string
actually leveraging the textwrap.dedent functionality
Expand Down Expand Up @@ -587,12 +622,14 @@ barks""",
def dastardly_default_value(
cow: String = json.loads("""this
cow: String = json.loads(
"""this
is
quite
the
dastadardly
value!"""),
value!"""
),
**kwargs,
):
pass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8213,20 +8213,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -480,10 +480,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -958,13 +956,11 @@
@@ -958,13 +958,11 @@
Do cool stuff.

``````
Expand Down Expand Up @@ -9617,20 +9604,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -480,10 +480,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -958,13 +956,11 @@
@@ -958,13 +958,11 @@
Do cool stuff.

``````
Expand Down Expand Up @@ -11030,20 +11004,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -489,10 +489,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -967,13 +965,11 @@
@@ -967,13 +967,11 @@
Do cool stuff.

``````
Expand Down Expand Up @@ -12434,20 +12395,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -480,10 +480,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -958,13 +956,11 @@
@@ -958,13 +958,11 @@
Do cool stuff.

``````
Expand Down Expand Up @@ -13847,20 +13795,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -489,10 +489,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -967,13 +965,11 @@
@@ -967,13 +967,11 @@
Do cool stuff.

``````
Expand Down Expand Up @@ -15251,20 +15186,7 @@ def markdown_skipped_rst_directive():
```diff
--- Stable
+++ Preview
@@ -480,10 +480,8 @@
Do cool stuff::

if True:
- cool_stuff(
- '''
- hiya'''
- )
+ cool_stuff('''
+ hiya''')

Done.
"""
@@ -958,13 +956,11 @@
@@ -958,13 +958,11 @@
Do cool stuff.

``````
Expand Down

0 comments on commit b849250

Please sign in to comment.