Skip to content

Commit

Permalink
Format single string part
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Jun 22, 2023
1 parent 52dc57e commit ae9cadf
Show file tree
Hide file tree
Showing 22 changed files with 632 additions and 344 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions crates/ruff_python_ast/src/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,15 @@ pub fn is_implicit_concatenation(content: &str) -> bool {
let mut rest = &content[leading_quote_str.len()..content.len() - trailing_quote_str.len()];
while let Some(index) = rest.find(trailing_quote_str) {
let mut chars = rest[..index].chars().rev();

if let Some('\\') = chars.next() {
// If the quote is double-escaped, then it's _not_ escaped, so the string is
// implicitly concatenated.
if let Some('\\') = chars.next() {
return true;
if chars.next() == Some('\\') {
// Either `\\'` or `\\\'` need to test one more character

// If the quote is preceded by `//` then it is not escaped, instead the backslash is escaped.
if chars.next() != Some('\\') {
return true;
}
}
} else {
// If the quote is _not_ escaped, then it's implicitly concatenated.
Expand Down Expand Up @@ -299,5 +303,6 @@ mod tests {

// Negative cases with escaped quotes.
assert!(!is_implicit_concatenation(r#""abc\"def""#));
assert!(!is_implicit_concatenation(r#"'\\\' ""'"#));
}
}
1 change: 1 addition & 0 deletions crates/ruff_python_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ruff_python_ast = { path = "../ruff_python_ast" }
ruff_text_size = { workspace = true }

anyhow = { workspace = true }
bitflags = { workspace = true }
clap = { workspace = true }
countme = "3.0.1"
is-macro = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"' test"
'" test'

"\" test"
'\' test'

# Prefer single quotes for string with more double quotes
"' \" \" '' \" \" '"

# Prefer double quotes for string with more single quotes
'\' " " \'\' " " \''

# Prefer double quotes for string with equal amount of single and double quotes
'" \' " " \'\''
"' \" '' \" \" '"

"\\' \"\""
'\\\' ""'


u"Test"
U"Test"

r"Test"
R"Test"

'This string will not include \
backslashes or newline characters.'

if True:
'This string will not include \
backslashes or newline characters.'

"""Multiline
String \"
"""

'''Multiline
String \'
'''
31 changes: 4 additions & 27 deletions crates/ruff_python_formatter/src/expression/expr_constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ use crate::comments::Comments;
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::expression::string::FormatString;
use crate::prelude::*;
use crate::trivia::SimpleTokenizer;
use crate::{not_yet_implemented_custom_text, verbatim_text, FormatNodeRule};
use ruff_formatter::{write, FormatContext, FormatError};
use ruff_python_ast::str::{is_implicit_concatenation, leading_quote};
use ruff_text_size::TextRange;
use rustpython_parser::ast::{Constant, ExprConstant, Ranged};
use rustpython_parser::lexer::{lex_starts_at, Lexer};
use rustpython_parser::{Mode, Tok};
use ruff_formatter::write;
use rustpython_parser::ast::{Constant, ExprConstant};

#[derive(Default)]
pub struct FormatExprConstant;
Expand All @@ -33,7 +29,7 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. } => {
write!(f, [verbatim_text(item)])
}
Constant::Str(_) => FormatString { constant: item }.fmt(f),
Constant::Str(_) => FormatString::new(item).fmt(f),
Constant::Bytes(_) => {
not_yet_implemented_custom_text(r#"b"NOT_YET_IMPLEMENTED_BYTE_STRING""#).fmt(f)
}
Expand Down Expand Up @@ -73,22 +69,3 @@ impl NeedsParentheses for ExprConstant {
}
}
}

struct FormatString<'a> {
constant: &'a ExprConstant,
}

impl Format<PyFormatContext<'_>> for FormatString<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let constant = self.constant;
debug_assert!(constant.value.is_str());

let string_content = f.context().locator().slice(constant.range());

if is_implicit_concatenation(string_content) {
not_yet_implemented_custom_text(r#""NOT_YET_IMPLEMENTED_STRING""#).fmt(f)
} else {
source_text_slice(constant.range(), ContainsNewlines::Detect).fmt(f)
}
}
}
1 change: 1 addition & 0 deletions crates/ruff_python_formatter/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub(crate) mod expr_unary_op;
pub(crate) mod expr_yield;
pub(crate) mod expr_yield_from;
pub(crate) mod parentheses;
mod string;

#[derive(Default)]
pub struct FormatExpr {
Expand Down
Loading

0 comments on commit ae9cadf

Please sign in to comment.