From 0a3ed21a5fa2f7c8802adb288f31feac5836f1dd Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 22 Jun 2023 14:19:48 +0200 Subject: [PATCH] Format single string part --- Cargo.lock | 1 + crates/ruff_python_formatter/Cargo.toml | 1 + .../test/fixtures/ruff/expression/string.py | 29 ++ .../src/expression/expr_constant.rs | 31 +-- .../src/expression/mod.rs | 1 + .../src/expression/string.rs | 249 ++++++++++++++++++ crates/ruff_python_formatter/src/lib.rs | 35 +++ ...r__tests__black_test__bracketmatch_py.snap | 5 +- ...er__tests__black_test__collections_py.snap | 10 +- ...tter__tests__black_test__comments2_py.snap | 74 ++---- ...sts__black_test__docstring_preview_py.snap | 8 +- ...tter__tests__black_test__docstring_py.snap | 108 +++----- ...er__tests__black_test__empty_lines_py.snap | 38 +-- ...ter__tests__black_test__expression_py.snap | 100 ++++--- ...atter__tests__black_test__fmtonoff_py.snap | 78 +++--- ...atter__tests__black_test__function_py.snap | 13 +- ...lack_test__function_trailing_comma_py.snap | 26 +- ...tests__black_test__string_prefixes_py.snap | 5 +- ...t__trailing_comma_optional_parens1_py.snap | 4 +- ...t__trailing_comma_optional_parens2_py.snap | 4 +- ...sts__ruff_test__expression__string_py.snap | 73 +++++ 21 files changed, 574 insertions(+), 319 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/string.py create mode 100644 crates/ruff_python_formatter/src/expression/string.rs create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__string_py.snap diff --git a/Cargo.lock b/Cargo.lock index 43e96c3a5a116d..61791812a69232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2034,6 +2034,7 @@ name = "ruff_python_formatter" version = "0.0.0" dependencies = [ "anyhow", + "bitflags 2.3.1", "clap", "countme", "insta", diff --git a/crates/ruff_python_formatter/Cargo.toml b/crates/ruff_python_formatter/Cargo.toml index 708b77c8f94aa6..0701907975ba5a 100644 --- a/crates/ruff_python_formatter/Cargo.toml +++ b/crates/ruff_python_formatter/Cargo.toml @@ -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 } diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/string.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/string.py new file mode 100644 index 00000000000000..56f0a774d4642f --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/string.py @@ -0,0 +1,29 @@ +"' 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.' diff --git a/crates/ruff_python_formatter/src/expression/expr_constant.rs b/crates/ruff_python_formatter/src/expression/expr_constant.rs index 4ff4f1620fbe10..d1d7de417b1133 100644 --- a/crates/ruff_python_formatter/src/expression/expr_constant.rs +++ b/crates/ruff_python_formatter/src/expression/expr_constant.rs @@ -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; @@ -33,7 +29,7 @@ impl FormatNodeRule 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) } @@ -73,22 +69,3 @@ impl NeedsParentheses for ExprConstant { } } } - -struct FormatString<'a> { - constant: &'a ExprConstant, -} - -impl Format> for FormatString<'_> { - fn fmt(&self, f: &mut Formatter>) -> 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) - } - } -} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 46214396fc2816..06292c64477b20 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -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 { diff --git a/crates/ruff_python_formatter/src/expression/string.rs b/crates/ruff_python_formatter/src/expression/string.rs new file mode 100644 index 00000000000000..92e9bd84c81450 --- /dev/null +++ b/crates/ruff_python_formatter/src/expression/string.rs @@ -0,0 +1,249 @@ +use crate::prelude::*; +use crate::{not_yet_implemented_custom_text, QuoteStyle}; +use bitflags::bitflags; +use ruff_formatter::{write, FormatError}; +use ruff_python_ast::str::is_implicit_concatenation; +use ruff_text_size::{TextLen, TextRange, TextSize}; +use rustpython_parser::ast::{ExprConstant, Ranged}; +use std::borrow::Cow; + +pub(super) struct FormatString { + string_range: TextRange, +} + +impl FormatString { + pub(super) fn new(constant: &ExprConstant) -> Self { + debug_assert!(constant.value.is_str()); + Self { + string_range: constant.range(), + } + } +} + +impl Format> for FormatString { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + let string_content = f.context().locator().slice(self.string_range); + + if is_implicit_concatenation(string_content) { + not_yet_implemented_custom_text(r#""NOT_YET_IMPLEMENTED" "IMPLICIT_CONCATENATION""#) + .fmt(f) + } else { + FormatStringPart::new(self.string_range).fmt(f) + } + } +} + +struct FormatStringPart { + part_range: TextRange, +} + +impl FormatStringPart { + const fn new(range: TextRange) -> Self { + Self { part_range: range } + } +} + +impl Format> for FormatStringPart { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + let string_content = f.context().locator().slice(self.part_range); + + let prefix = StringPrefix::parse(string_content); + let after_prefix = &string_content[usize::from(prefix.text_len())..]; + + let quotes = StringQuotes::parse(after_prefix).ok_or(FormatError::SyntaxError)?; + let relative_raw_content_range = TextRange::new( + prefix.text_len() + quotes.text_len(), + string_content.text_len() - quotes.text_len(), + ); + let raw_content_range = relative_raw_content_range + self.part_range.start(); + + let raw_content = &string_content[relative_raw_content_range]; + let preferred_quote = preferred_quotes(raw_content); + + let preferred_quotes = StringQuotes { + style: preferred_quote, + triple: quotes.triple, + }; + + write!(f, [prefix, preferred_quotes])?; + + let normalized = normalize_quotes(raw_content, preferred_quote); + + match normalized { + Cow::Borrowed(_) => { + source_text_slice(raw_content_range, ContainsNewlines::Detect).fmt(f)?; + } + Cow::Owned(normalized) => { + dynamic_text(&normalized, Some(raw_content_range.start())).fmt(f)?; + } + } + + preferred_quotes.fmt(f) + } +} + +bitflags! { + #[derive(Copy, Clone, Debug)] + struct StringPrefix: u8 { + const UNICODE = 0b0000_0001; + /// `r"test"` + const RAW = 0b0000_0010; + /// `R"test" + const RAW_UPPER = 0b0000_0100; + const BYTE = 0b0000_1000; + const F_STRING = 0b0001_0000; + } +} + +impl StringPrefix { + fn parse(input: &str) -> StringPrefix { + let chars = input.chars(); + let mut prefix = StringPrefix::empty(); + + for c in chars { + let flag = match c { + 'u' | 'U' => StringPrefix::UNICODE, + 'f' | 'F' => StringPrefix::F_STRING, + 'b' | 'B' => StringPrefix::BYTE, + 'r' => StringPrefix::RAW, + 'R' => StringPrefix::RAW_UPPER, + _ => break, + }; + + prefix |= flag; + } + + prefix + } + + const fn text_len(self) -> TextSize { + TextSize::new(self.bits().count_ones()) + } +} + +impl Format> for StringPrefix { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + if self.contains(StringPrefix::RAW) { + text("r").fmt(f)?; + } else if self.contains(StringPrefix::RAW_UPPER) { + text("R").fmt(f)?; + } + + if self.contains(StringPrefix::BYTE) { + text("b").fmt(f)?; + } + + if self.contains(StringPrefix::F_STRING) { + text("f").fmt(f)?; + } + + // Drop unicode + + Ok(()) + } +} + +/// Detects the preferred quotes for `input`. The preferred quote style is the one that +/// requires less escape sequences. +fn preferred_quotes(input: &str) -> QuoteStyle { + let mut single_quotes = 0u32; + let mut double_quotes = 0u32; + let mut chars = input.chars(); + + while let Some(c) = chars.next() { + let style = match c { + '\\' => chars.next().ok_or(()).and_then(QuoteStyle::try_from), + c => QuoteStyle::try_from(c), + }; + + match style { + Ok(QuoteStyle::Single) => { + single_quotes += 1; + } + Ok(QuoteStyle::Double) => { + double_quotes += 1; + } + Err(_) => {} + } + } + + if double_quotes > single_quotes { + QuoteStyle::Single + } else { + QuoteStyle::Double + } +} + +struct StringQuotes { + triple: bool, + style: QuoteStyle, +} + +impl StringQuotes { + fn parse(input: &str) -> Option { + let mut chars = input.chars(); + + let quote_char = chars.next()?; + let style = QuoteStyle::try_from(quote_char).ok()?; + + let triple = chars.next() == Some(quote_char) && chars.next() == Some(quote_char); + + Some(Self { triple, style }) + } + + const fn text_len(&self) -> TextSize { + if self.triple { + TextSize::new(3) + } else { + TextSize::new(1) + } + } +} + +impl Format> for StringQuotes { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + let quotes = match (self.style, self.triple) { + (QuoteStyle::Single, false) => "'", + (QuoteStyle::Single, true) => "'''", + (QuoteStyle::Double, false) => "\"", + (QuoteStyle::Double, true) => "\"\"\"", + }; + + text(quotes).fmt(f) + } +} + +fn normalize_quotes(input: &str, style: QuoteStyle) -> Cow { + let mut output = String::new(); + + let mut chars = input.char_indices(); + + let preferred_quote = style.as_char(); + let opposite_quote = style.opposite().as_char(); + let mut last_index = 0; + + while let Some((index, c)) = chars.next() { + if c == '\\' + && chars + .next() + .map_or(false, |(_, next)| next == opposite_quote) + { + // Remove the escape + output.push_str(&input[last_index..index]); + last_index = index + '\\'.len_utf8(); + } else if c == preferred_quote { + // Escape the quote + output.push_str(&input[last_index..index]); + output.push('\\'); + output.push(c); + last_index = index + preferred_quote.len_utf8(); + } + } + + if last_index == 0 { + Cow::Borrowed(input) + } else { + output.push_str(&input[last_index..]); + Cow::Owned(output) + } +} diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 0faf824f093ba6..5c9009858e04ba 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -226,6 +226,41 @@ impl Format> for VerbatimText { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum QuoteStyle { + Single, + Double, +} + +impl QuoteStyle { + pub const fn as_char(self) -> char { + match self { + QuoteStyle::Single => '\'', + QuoteStyle::Double => '"', + } + } + + #[must_use] + pub const fn opposite(self) -> QuoteStyle { + match self { + QuoteStyle::Single => QuoteStyle::Double, + QuoteStyle::Double => QuoteStyle::Single, + } + } +} + +impl TryFrom for QuoteStyle { + type Error = (); + + fn try_from(value: char) -> std::result::Result { + match value { + '\'' => Ok(QuoteStyle::Single), + '"' => Ok(QuoteStyle::Double), + _ => Err(()), + } + } +} + #[cfg(test)] mod tests { use anyhow::Result; diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap index 1a44e5bdc2c043..548945997cbf71 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap @@ -20,8 +20,7 @@ lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x --- Black +++ Ruff @@ -1,4 +1,4 @@ --for ((x in {}) or {})["a"] in x: -+for ((x in {}) or {})['a'] in x: + for ((x in {}) or {})["a"] in x: pass -pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) -lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x @@ -32,7 +31,7 @@ lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x ## Ruff Output ```py -for ((x in {}) or {})['a'] in x: +for ((x in {}) or {})["a"] in x: pass pem_spam = lambda x: True lambda x: True diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap index 849bd22c63cf3f..1c961a36e7dace 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap @@ -134,13 +134,7 @@ if True: nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)} nested_long_lines = [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", -@@ -47,15 +29,12 @@ - "oneple": (1,), - } - {"oneple": (1,)} --["ls", "lsoneple/%s" % (foo,)] -+['ls', 'lsoneple/%s' % (foo,)] - x = {"oneple": (1,)} +@@ -52,10 +34,7 @@ y = { "oneple": (1,), } @@ -230,7 +224,7 @@ nested_long_lines = [ "oneple": (1,), } {"oneple": (1,)} -['ls', 'lsoneple/%s' % (foo,)] +["ls", "lsoneple/%s" % (foo,)] x = {"oneple": (1,)} y = { "oneple": (1,), diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap index 47bc43c5ba1450..07c15327b028c7 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap @@ -178,7 +178,7 @@ instruction()#comment with bad spacing ```diff --- Black +++ Ruff -@@ -1,31 +1,27 @@ +@@ -1,9 +1,5 @@ -from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent, # NOT DRY -) @@ -190,43 +190,6 @@ instruction()#comment with bad spacing # Please keep __all__ alphabetized within each category. - __all__ = [ - # Super-special typing primitives. -- "Any", -- "Callable", -- "ClassVar", -+ 'Any', -+ 'Callable', -+ 'ClassVar', - # ABCs (from collections.abc). -- "AbstractSet", # collections.abc.Set. -- "ByteString", -- "Container", -+ 'AbstractSet', # collections.abc.Set. -+ 'ByteString', -+ 'Container', - # Concrete collection types. -- "Counter", -- "Deque", -- "Dict", -- "DefaultDict", -- "List", -- "Set", -- "FrozenSet", -- "NamedTuple", # Not really a type. -- "Generator", -+ 'Counter', -+ 'Deque', -+ 'Dict', -+ 'DefaultDict', -+ 'List', -+ 'Set', -+ 'FrozenSet', -+ 'NamedTuple', # Not really a type. -+ 'Generator', - ] - - not_shareables = [ @@ -37,31 +33,35 @@ # builtin types and objects type, @@ -246,9 +209,8 @@ instruction()#comment with bad spacing + NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg), ] --if "PYTHON" in os.environ: + if "PYTHON" in os.environ: - add_compiler(compiler_from_env()) -+if 'PYTHON' in os.environ: + NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) else: # for compiler in compilers.values(): @@ -399,23 +361,23 @@ NOT_YET_IMPLEMENTED_StmtImportFrom __all__ = [ # Super-special typing primitives. - 'Any', - 'Callable', - 'ClassVar', + "Any", + "Callable", + "ClassVar", # ABCs (from collections.abc). - 'AbstractSet', # collections.abc.Set. - 'ByteString', - 'Container', + "AbstractSet", # collections.abc.Set. + "ByteString", + "Container", # Concrete collection types. - 'Counter', - 'Deque', - 'Dict', - 'DefaultDict', - 'List', - 'Set', - 'FrozenSet', - 'NamedTuple', # Not really a type. - 'Generator', + "Counter", + "Deque", + "Dict", + "DefaultDict", + "List", + "Set", + "FrozenSet", + "NamedTuple", # Not really a type. + "Generator", ] not_shareables = [ @@ -438,7 +400,7 @@ not_shareables = [ NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg), ] -if 'PYTHON' in os.environ: +if "PYTHON" in os.environ: NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) else: # for compiler in compilers.values(): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_preview_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_preview_py.snap index d565b0d9e88ba0..296bb4715de254 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_preview_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_preview_py.snap @@ -108,12 +108,6 @@ def single_quote_docstring_over_line_limit2(): def single_quote_docstring_over_line_limit(): -@@ -45,4 +41,4 @@ - - - def single_quote_docstring_over_line_limit2(): -- "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." -+ 'We do not want to put the closing quote on a new line as that is invalid (see GH-3141).' ``` ## Ruff Output @@ -162,7 +156,7 @@ def single_quote_docstring_over_line_limit(): def single_quote_docstring_over_line_limit2(): - 'We do not want to put the closing quote on a new line as that is invalid (see GH-3141).' + "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap index d1f1554d676493..80203f80efca49 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap @@ -263,12 +263,11 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): def bar(): -- """This is another docstring + """This is another docstring - with more lines of text - """ -+ '''This is another docstring + with more lines of text -+ ''' ++ """ return @@ -280,12 +279,11 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): def troz(): -- """Indentation with tabs + """Indentation with tabs - is just as OK - """ -+ '''Indentation with tabs + is just as OK -+ ''' ++ """ return @@ -353,12 +351,19 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): pass -@@ -93,20 +95,25 @@ +@@ -88,25 +90,30 @@ + + + def that(): +- """ "hey yah" """ ++ ''' "hey yah" ''' + def and_that(): - """ +- """ - "hey yah" """ -+ "hey yah" """ ++ ''' ++ "hey yah" ''' def and_this(): @@ -370,26 +375,21 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): def multiline_whitespace(): - """ """ -+ ''' ++ """ + + + + -+ ''' ++ """ def oneline_whitespace(): - """ """ -+ ''' ''' ++ """ """ def empty(): -@@ -114,12 +121,12 @@ - - - def single_quotes(): -- "testing" -+ 'testing' +@@ -118,8 +125,8 @@ def believe_it_or_not_this_is_in_the_py_stdlib(): @@ -445,33 +445,21 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): pass -@@ -161,14 +168,14 @@ - - - def multiline_backslash_1(): -- """ -+ ''' - hey\there\ -- \ """ -+ \ ''' - +@@ -168,7 +175,7 @@ def multiline_backslash_2(): -- """ + """ - hey there \ """ -+ ''' -+ hey there \ ''' ++ hey there \ """ # Regression test for #3425 -@@ -178,8 +185,8 @@ - +@@ -179,7 +186,7 @@ def multiline_backslash_3(): -- """ + """ - already escaped \\""" -+ ''' -+ already escaped \\ ''' ++ already escaped \\ """ def my_god_its_full_of_stars_1(): @@ -484,16 +472,6 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): def docstring_almost_at_line_limit(): -@@ -213,7 +220,7 @@ - - - def stable_quote_normalization_with_immediate_inner_single_quote(self): -- """' -+ '''' - - -- """ -+ ''' ``` ## Ruff Output @@ -519,9 +497,9 @@ def foo(): def bar(): - '''This is another docstring + """This is another docstring with more lines of text - ''' + """ return @@ -532,9 +510,9 @@ def baz(): def troz(): - '''Indentation with tabs + """Indentation with tabs is just as OK - ''' + """ return @@ -591,12 +569,12 @@ def this(): def that(): - """ "hey yah" """ + ''' "hey yah" ''' def and_that(): - """ - "hey yah" """ + ''' + "hey yah" ''' def and_this(): @@ -605,16 +583,16 @@ def and_this(): def multiline_whitespace(): - ''' + """ - ''' + """ def oneline_whitespace(): - ''' ''' + """ """ def empty(): @@ -622,7 +600,7 @@ def empty(): def single_quotes(): - 'testing' + "testing" def believe_it_or_not_this_is_in_the_py_stdlib(): @@ -669,14 +647,14 @@ def backslash_space(): def multiline_backslash_1(): - ''' + """ hey\there\ - \ ''' + \ """ def multiline_backslash_2(): - ''' - hey there \ ''' + """ + hey there \ """ # Regression test for #3425 @@ -686,8 +664,8 @@ def multiline_backslash_really_long_dont_crash(): def multiline_backslash_3(): - ''' - already escaped \\ ''' + """ + already escaped \\ """ def my_god_its_full_of_stars_1(): @@ -721,10 +699,10 @@ def multiline_docstring_at_line_limit(): def stable_quote_normalization_with_immediate_inner_single_quote(self): - '''' + """' - ''' + """ ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap index cec4af573cc64d..874c26f4618bc2 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap @@ -105,19 +105,6 @@ def g(): ```diff --- Black +++ Ruff -@@ -3,9 +3,9 @@ - - # leading comment - def f(): -- NO = "" -- SPACE = " " -- DOUBLESPACE = " " -+ NO = '' -+ SPACE = ' ' -+ DOUBLESPACE = ' ' - - t = leaf.type - p = leaf.parent # trailing comment @@ -16,32 +16,40 @@ if t == token.COMMENT: # another trailing comment return DOUBLESPACE @@ -175,21 +162,14 @@ def g(): return NO -@@ -49,11 +57,10 @@ +@@ -49,7 +57,6 @@ # SECTION BECAUSE SECTIONS ############################################################################### - def g(): -- NO = "" -- SPACE = " " -- DOUBLESPACE = " " -+ NO = '' -+ SPACE = ' ' -+ DOUBLESPACE = ' ' - - t = leaf.type - p = leaf.parent + NO = "" + SPACE = " " @@ -67,11 +74,11 @@ return DOUBLESPACE @@ -237,9 +217,9 @@ def g(): # leading comment def f(): - NO = '' - SPACE = ' ' - DOUBLESPACE = ' ' + NO = "" + SPACE = " " + DOUBLESPACE = " " t = leaf.type p = leaf.parent # trailing comment @@ -292,9 +272,9 @@ def f(): ############################################################################### def g(): - NO = '' - SPACE = ' ' - DOUBLESPACE = ' ' + NO = "" + SPACE = " " + DOUBLESPACE = " " t = leaf.type p = leaf.parent diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap index 7c4fbdde7a8232..d64a1abf8ac63d 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap @@ -268,10 +268,9 @@ last_call() --- Black +++ Ruff @@ -1,5 +1,6 @@ --"some_string" --b"\\xa3" +... -+'some_string' + "some_string" +-b"\\xa3" +b"NOT_YET_IMPLEMENTED_BYTE_STRING" Name None @@ -298,6 +297,11 @@ last_call() -(str or None) if True else (str or bytes or None) -str or None if (1 if True else 2) else str or bytes or None -(str or None) if (1 if True else 2) else (str or bytes or None) +-( +- (super_long_variable_name or None) +- if (1 if super_long_test_name else 2) +- else (str or bytes or None) +-) +lambda x: True +lambda x: True +lambda x: True @@ -311,31 +315,24 @@ last_call() +NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) -+{'2.7': dead, '3.7': (long_live or die_hard)} -+{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} -+{**a, **b, **c} + {"2.7": dead, "3.7": (long_live or die_hard)} + {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} + {**a, **b, **c} +-{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} +-({"a": "b"}, (True or False), (+value), "string", b"bytes") or None +{ -+ '2.7', -+ '3.6', -+ '3.7', -+ '3.8', -+ '3.9', ++ "2.7", ++ "3.6", ++ "3.7", ++ "3.8", ++ "3.9", + (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false), +} - ( -- (super_long_variable_name or None) -- if (1 if super_long_test_name else 2) -- else (str or bytes or None) --) --{"2.7": dead, "3.7": (long_live or die_hard)} --{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} --{**a, **b, **c} --{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} --({"a": "b"}, (True or False), (+value), "string", b"bytes") or None -+ {'a': 'b'}, ++( ++ {"a": "b"}, + (True or False), + (+value), -+ 'string', ++ "string", + b"NOT_YET_IMPLEMENTED_BYTE_STRING", +) or None () @@ -356,17 +353,17 @@ last_call() - 4, - 5, -] --[ -- 4, -- *a, -- 5, --] +[1, 2, 3] +[NOT_YET_IMPLEMENTED_ExprStarred] +[NOT_YET_IMPLEMENTED_ExprStarred] +[NOT_YET_IMPLEMENTED_ExprStarred, 4, 5] +[4, NOT_YET_IMPLEMENTED_ExprStarred, 5] [ +- 4, +- *a, +- 5, +-] +-[ this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, @@ -492,16 +489,15 @@ last_call() +numpy[:, :: -1] numpy[np.newaxis, :] -(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) --{"2.7": dead, "3.7": long_live or die_hard} --{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} +NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+{'2.7': dead, '3.7': long_live or die_hard} + {"2.7": dead, "3.7": long_live or die_hard} +-{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} +{ -+ '2.7', -+ '3.6', -+ '3.7', -+ '3.8', -+ '3.9', ++ "2.7", ++ "3.6", ++ "3.7", ++ "3.8", ++ "3.9", + NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, +} [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] @@ -689,7 +685,7 @@ last_call() ```py ... -'some_string' +"some_string" b"NOT_YET_IMPLEMENTED_BYTE_STRING" Name None @@ -736,22 +732,22 @@ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) -{'2.7': dead, '3.7': (long_live or die_hard)} -{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} +{"2.7": dead, "3.7": (long_live or die_hard)} +{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} { - '2.7', - '3.6', - '3.7', - '3.8', - '3.9', + "2.7", + "3.6", + "3.7", + "3.8", + "3.9", (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false), } ( - {'a': 'b'}, + {"a": "b"}, (True or False), (+value), - 'string', + "string", b"NOT_YET_IMPLEMENTED_BYTE_STRING", ) or None () @@ -844,13 +840,13 @@ numpy[:, l[-2]] numpy[:, :: -1] numpy[np.newaxis, :] NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -{'2.7': dead, '3.7': long_live or die_hard} +{"2.7": dead, "3.7": long_live or die_hard} { - '2.7', - '3.6', - '3.7', - '3.8', - '3.9', + "2.7", + "3.6", + "3.7", + "3.8", + "3.9", NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, } [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap index 8011271d1d1ab9..3bf99903997a6e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap @@ -253,7 +253,7 @@ d={'a':1, - async with some_connection() as conn: - await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) - await asyncio.sleep(1) -+ 'Single-line docstring. Multiline is harder to reformat.' ++ "Single-line docstring. Multiline is harder to reformat." + NOT_YET_IMPLEMENTED_StmtAsyncWith + await NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) + @@ -269,7 +269,7 @@ d={'a':1, +def function_signature_stress_test( + number: int, + no_annotation=None, -+ text: str = 'default', ++ text: str = "default", + *, + debug: bool = False, + **kwargs, @@ -290,22 +290,21 @@ d={'a':1, + f=-1, + g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + h="", -+ i=r'', ++ i=r"", +): + offset = NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) + NOT_YET_IMPLEMENTED_StmtAssert def spaces_types( -@@ -51,14 +72,14 @@ +@@ -51,68 +72,66 @@ d: dict = {}, e: bool = True, f: int = -1, - g: int = 1 if False else 2, + g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h: str = "", -- i: str = r"", -+ i: str = r'', + i: str = r"", ): ... @@ -315,7 +314,13 @@ d={'a':1, ... -@@ -71,48 +92,46 @@ + something = { + # fmt: off +- key: 'value', ++ key: "value", + } + + def subscriptlist(): atom[ # fmt: off @@ -325,8 +330,8 @@ d={'a':1, - goes + here, - andhere, + ( -+ 'some big and', -+ 'complex subscript', ++ "some big and", ++ "complex subscript", + # fmt: on + goes + + here, @@ -338,31 +343,34 @@ d={'a':1, def import_as_names(): # fmt: off - from hello import a, b +- 'unformatted' + NOT_YET_IMPLEMENTED_StmtImportFrom - 'unformatted' ++ "unformatted" # fmt: on def testlist_star_expr(): # fmt: off - a , b = *hello +- 'unformatted' + a, b = NOT_YET_IMPLEMENTED_ExprStarred - 'unformatted' ++ "unformatted" # fmt: on def yield_expr(): # fmt: off - yield hello +- 'unformatted' + NOT_YET_IMPLEMENTED_ExprYield - 'unformatted' ++ "unformatted" # fmt: on -- "formatted" -+ 'formatted' + "formatted" # fmt: off - ( yield hello ) +- 'unformatted' + (NOT_YET_IMPLEMENTED_ExprYield) - 'unformatted' ++ "unformatted" # fmt: on @@ -405,13 +413,11 @@ d={'a':1, - implicit_default=True, - ) - ) -+ NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) - # fmt: off +- # fmt: off - a = ( - unnecessary_bracket() - ) -+ a = NOT_IMPLEMENTED_call() - # fmt: on +- # fmt: on - _type_comment_re = re.compile( - r""" - ^ @@ -432,9 +438,11 @@ d={'a':1, - ) - $ - """, -- # fmt: off ++ NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) + # fmt: off - re.MULTILINE|re.VERBOSE -- # fmt: on ++ a = NOT_IMPLEMENTED_call() + # fmt: on - ) + _type_comment_re = NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) @@ -480,7 +488,7 @@ d={'a':1, -d={'a':1, - 'b':2} +l = [1, 2, 3] -+d = {'a': 1, 'b': 2} ++d = {"a": 1, "b": 2} ``` ## Ruff Output @@ -519,7 +527,7 @@ def func_no_args(): async def coroutine(arg, exec=False): - 'Single-line docstring. Multiline is harder to reformat.' + "Single-line docstring. Multiline is harder to reformat." NOT_YET_IMPLEMENTED_StmtAsyncWith await NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) @@ -529,7 +537,7 @@ async def coroutine(arg, exec=False): def function_signature_stress_test( number: int, no_annotation=None, - text: str = 'default', + text: str = "default", *, debug: bool = False, **kwargs, @@ -547,7 +555,7 @@ def spaces( f=-1, g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h="", - i=r'', + i=r"", ): offset = NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) NOT_YET_IMPLEMENTED_StmtAssert @@ -562,7 +570,7 @@ def spaces_types( f: int = -1, g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h: str = "", - i: str = r'', + i: str = r"", ): ... @@ -573,7 +581,7 @@ def spaces2(result=NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg)): something = { # fmt: off - key: 'value', + key: "value", } @@ -581,8 +589,8 @@ def subscriptlist(): atom[ # fmt: off ( - 'some big and', - 'complex subscript', + "some big and", + "complex subscript", # fmt: on goes + here, @@ -594,26 +602,26 @@ def subscriptlist(): def import_as_names(): # fmt: off NOT_YET_IMPLEMENTED_StmtImportFrom - 'unformatted' + "unformatted" # fmt: on def testlist_star_expr(): # fmt: off a, b = NOT_YET_IMPLEMENTED_ExprStarred - 'unformatted' + "unformatted" # fmt: on def yield_expr(): # fmt: off NOT_YET_IMPLEMENTED_ExprYield - 'unformatted' + "unformatted" # fmt: on - 'formatted' + "formatted" # fmt: off (NOT_YET_IMPLEMENTED_ExprYield) - 'unformatted' + "unformatted" # fmt: on @@ -669,7 +677,7 @@ NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) NOT_YET_IMPLEMENTED_ExprYield # No formatting to the end of the file l = [1, 2, 3] -d = {'a': 1, 'b': 2} +d = {"a": 1, "b": 2} ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap index 38ae6f3607c9b3..da23fa784a0197 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap @@ -116,10 +116,10 @@ def __await__(): return (yield) +NOT_YET_IMPLEMENTED_StmtImport -from third_party import X, Y, Z -- --from library import some_connection, some_decorator +NOT_YET_IMPLEMENTED_StmtImportFrom +-from library import some_connection, some_decorator +- -f"trigger 3.6 mode" +NOT_YET_IMPLEMENTED_StmtImportFrom +NOT_YET_IMPLEMENTED_ExprJoinedStr @@ -179,7 +179,7 @@ def __await__(): return (yield) + f=-1, + g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + h="", -+ i=r'', ++ i=r"", +): + offset = NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) + NOT_YET_IMPLEMENTED_StmtAssert @@ -193,8 +193,7 @@ def __await__(): return (yield) - g: int = 1 if False else 2, + g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h: str = "", -- i: str = r"", -+ i: str = r'', + i: str = r"", ): ... @@ -340,7 +339,7 @@ def spaces( f=-1, g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h="", - i=r'', + i=r"", ): offset = NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) NOT_YET_IMPLEMENTED_StmtAssert @@ -355,7 +354,7 @@ def spaces_types( f: int = -1, g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, h: str = "", - i: str = r'', + i: str = r"", ): ... diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap index 07865c4627fc2f..e117c52399e518 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap @@ -74,26 +74,6 @@ some_module.some_function( ```diff --- Black +++ Ruff -@@ -2,7 +2,7 @@ - a, - ): - d = { -- "key": "value", -+ 'key': 'value', - } - tup = (1,) - -@@ -12,8 +12,8 @@ - b, - ): - d = { -- "key": "value", -- "key2": "value2", -+ 'key': 'value', -+ 'key2': 'value2', - } - tup = ( - 1, @@ -24,18 +24,14 @@ def f( a: int = 1, @@ -198,7 +178,7 @@ def f( a, ): d = { - 'key': 'value', + "key": "value", } tup = (1,) @@ -208,8 +188,8 @@ def f2( b, ): d = { - 'key': 'value', - 'key2': 'value2', + "key": "value", + "key2": "value2", } tup = ( 1, diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap index 230f104239dfda..1af332dee31a09 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap @@ -39,10 +39,9 @@ def docstring_multiline(): name = "Łukasz" -(f"hello {name}", f"hello {name}") -(b"", b"") --("", "") +(NOT_YET_IMPLEMENTED_ExprJoinedStr, NOT_YET_IMPLEMENTED_ExprJoinedStr) +(b"NOT_YET_IMPLEMENTED_BYTE_STRING", b"NOT_YET_IMPLEMENTED_BYTE_STRING") -+(u"", U"") + ("", "") (r"", R"") -(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"") @@ -80,7 +79,7 @@ def docstring_multiline(): name = "Łukasz" (NOT_YET_IMPLEMENTED_ExprJoinedStr, NOT_YET_IMPLEMENTED_ExprJoinedStr) (b"NOT_YET_IMPLEMENTED_BYTE_STRING", b"NOT_YET_IMPLEMENTED_BYTE_STRING") -(u"", U"") +("", "") (r"", R"") ( diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap index 960fa6298382a1..70f66ad05c20cf 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap @@ -70,7 +70,7 @@ class A: - self.min_length, - ) % {"min_length": self.min_length} + return NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) % { -+ 'min_length': self.min_length, ++ "min_length": self.min_length, + } @@ -112,7 +112,7 @@ if x: class X: def get_help_text(self): return NOT_IMPLEMENTED_call(NOT_IMPLEMENTED_arg) % { - 'min_length': self.min_length, + "min_length": self.min_length, } diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap index 15db029d3c1075..122f1f4cfbb572 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap @@ -23,7 +23,7 @@ if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or - 8, -) <= get_tk_patchlevel() < (8, 6): +if ( -+ NOT_IMPLEMENTED_call() >= (8, 6, 0, 'final') ++ NOT_IMPLEMENTED_call() >= (8, 6, 0, "final") + or (8, 5, 8) <= NOT_IMPLEMENTED_call() < (8, 6) +): pass @@ -33,7 +33,7 @@ if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or ```py if ( - NOT_IMPLEMENTED_call() >= (8, 6, 0, 'final') + NOT_IMPLEMENTED_call() >= (8, 6, 0, "final") or (8, 5, 8) <= NOT_IMPLEMENTED_call() < (8, 6) ): pass diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__string_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__string_py.snap new file mode 100644 index 00000000000000..a85c823ac45b7c --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__string_py.snap @@ -0,0 +1,73 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +--- +## Input +```py +"' 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.' +``` + + + +## Output +```py +"' 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 +"\" ' \" \" ''" +"' \" '' \" \" '" + + +"Test" +"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." +``` + +