From 6a79983cf553f755636787b9666ff5317e8c5da9 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 9 Feb 2024 18:09:29 -0500 Subject: [PATCH] Stabilize quote-style `preserve` This PR removes the `--preview` requirement for `quote-style: preserve` and changes the semantics of the style to apply to all strings (including triple quoted and doc-strings). The motivation for changing the semantic to apply to all strings is: * The option is intended as a replacement for black's `--skip-string-normalization` that applies to all strings * The option is intended for projects that can't enforce a consistent quote style. These are mainly projects that want to use a formatter but can't agree on a single quote style or changing all strings leads to too large diffs. Allowing these users to use a formatter is more important than being opinionated on the quote style * We received reports that users were surprised that `preserve` didn't apply to all strings. Fixes https://github.com/astral-sh/ruff/issues/9185 --- crates/ruff_python_formatter/src/options.rs | 6 +++++ .../src/other/string_literal.rs | 8 ++++--- .../ruff_python_formatter/src/string/mod.rs | 3 +++ .../snapshots/format@quote_style.py.snap | 23 +++++++++---------- crates/ruff_workspace/src/configuration.rs | 6 ----- crates/ruff_workspace/src/options.rs | 22 ++++++++++-------- ruff.schema.json | 2 +- 7 files changed, 38 insertions(+), 32 deletions(-) diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index 46f84d332d25e0..8deaf926e41125 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -248,6 +248,12 @@ pub enum QuoteStyle { Preserve, } +impl QuoteStyle { + pub const fn is_preserve(self) -> bool { + matches!(self, QuoteStyle::Preserve) + } +} + impl fmt::Display for QuoteStyle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/ruff_python_formatter/src/other/string_literal.rs b/crates/ruff_python_formatter/src/other/string_literal.rs index 3071f370986927..73044f84f8a57c 100644 --- a/crates/ruff_python_formatter/src/other/string_literal.rs +++ b/crates/ruff_python_formatter/src/other/string_literal.rs @@ -50,11 +50,13 @@ impl Format> for FormatStringLiteral<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { let locator = f.context().locator(); - let quote_style = if self.layout.is_docstring() { - // Per PEP 8 and PEP 257, always prefer double quotes for docstrings + let quote_style = f.options().quote_style(); + let quote_style = if self.layout.is_docstring() && !quote_style.is_preserve() { + // Per PEP 8 and PEP 257, always prefer double quotes for docstrings, + // except when using quote-style=preserve QuoteStyle::Double } else { - f.options().quote_style() + quote_style }; let normalized = StringPart::from_source(self.value.range(), &locator).normalize( diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index 40385d55dfdca6..b00d3c09ffc366 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -306,6 +306,7 @@ impl StringPart { normalize_hex: bool, ) -> NormalizedString<'a> { // Per PEP 8, always prefer double quotes for triple-quoted strings. + // Except when using quote-style-preserve. let preferred_style = if self.quotes.triple { // ... unless we're formatting a code snippet inside a docstring, // then we specifically want to invert our quote style to avoid @@ -354,6 +355,8 @@ impl StringPart { // if it doesn't have perfect alignment with PEP8. if let Some(quote) = parent_docstring_quote_char { QuoteStyle::from(quote.invert()) + } else if configured_style.is_preserve() { + QuoteStyle::Preserve } else { QuoteStyle::Double } diff --git a/crates/ruff_python_formatter/tests/snapshots/format@quote_style.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@quote_style.py.snap index 916295345ad7f0..9ee59a8d6d7e78 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@quote_style.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@quote_style.py.snap @@ -263,21 +263,21 @@ rb"rb double" rb'br single' rb"br double" -"""single triple""" +'''single triple''' """double triple""" -r"""r single triple""" +r'''r single triple''' r"""r double triple""" -f"""f single triple""" +f'''f single triple''' f"""f double triple""" -rf"""fr single triple""" +rf'''fr single triple''' rf"""fr double triple""" -rf"""rf single triple""" +rf'''rf single triple''' rf"""rf double triple""" -b"""b single triple""" +b'''b single triple''' b"""b double triple""" -rb"""rb single triple""" +rb'''rb single triple''' rb"""rb double triple""" -rb"""br single triple""" +rb'''br single triple''' rb"""br double triple""" 'single1' 'single2' @@ -287,7 +287,7 @@ rb"""br double triple""" def docstring_single_triple(): - """single triple""" + '''single triple''' def docstring_double_triple(): @@ -299,7 +299,7 @@ def docstring_double(): def docstring_single(): - "single" + 'single' ``` @@ -308,8 +308,7 @@ def docstring_single(): --- Stable +++ Preview @@ -1,4 +1,5 @@ --'single' # this string is treated as a docstring -+"single" # this string is treated as a docstring + 'single' # this string is treated as a docstring + "double" r'r single' diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index c41006b09e968b..5a37d0ca803cbd 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -166,12 +166,6 @@ impl Configuration { PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled, }; - if quote_style == QuoteStyle::Preserve && !format_preview.is_enabled() { - return Err(anyhow!( - "'quote-style = preserve' is a preview only feature. Run with '--preview' to enable it." - )); - } - let formatter = FormatterSettings { exclude: FilePatternSet::try_from_iter(format.exclude.unwrap_or_default())?, extension: self.extension.clone().unwrap_or_default(), diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 0a0ed48f47eb07..dd7761a30058fd 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2942,28 +2942,30 @@ pub struct FormatOptions { )] pub indent_style: Option, - /// Configures the preferred quote character for strings. Valid options are: - /// + /// Configures the preferred quote character for strings. The recommended options are /// * `double` (default): Use double quotes `"` /// * `single`: Use single quotes `'` - /// * `preserve` (preview only): Keeps the existing quote character. We don't recommend using this option except for projects - /// that already use a mixture of single and double quotes and can't migrate to using double or single quotes. /// /// In compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), - /// Ruff prefers double quotes for multiline strings and docstrings, regardless of the + /// Ruff prefers double quotes for triple quoted strings and docstrings, regardless of the /// configured quote style. /// - /// Ruff may also deviate from using the configured quotes if doing so requires - /// escaping quote characters within the string. For example, given: + /// Ruff deviates from using the configured quotes if doing so prevents the need for + /// escaping quote characters inside the string: /// /// ```python /// a = "a string without any quotes" /// b = "It's monday morning" /// ``` /// - /// Ruff will change `a` to use single quotes when using `quote-style = "single"`. However, - /// `b` remains unchanged, as converting to single quotes requires escaping the inner `'`, - /// which leads to less readable code: `'It\'s monday morning'`. This does not apply when using `preserve`. + /// Ruff will change the quotes of the string assigned to `a` to single quotes when using `quote-style = "single"`. + /// However, ruff uses double quotes for he string assigned to `b` because using single quotes would require escaping the `'`, + /// which leads to the less readable code: `'It\'s monday morning'`. + /// + /// In addition, Ruff supports the quote style `preserve` for projects that already use + /// a mixture of single and double quotes and can't migrate to the `double` or `single` style. + /// The quote style `preserve` leaves the quotes of all strings unchanged. + /// We don't recommend using `preserve` for new project because it doesn't enforce consistent quotes. #[option( default = r#"double"#, value_type = r#""double" | "single" | "preserve""#, diff --git a/ruff.schema.json b/ruff.schema.json index ec2abdb613faf0..aa6f71f008300b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1373,7 +1373,7 @@ ] }, "quote-style": { - "description": "Configures the preferred quote character for strings. Valid options are:\n\n* `double` (default): Use double quotes `\"` * `single`: Use single quotes `'` * `preserve` (preview only): Keeps the existing quote character. We don't recommend using this option except for projects that already use a mixture of single and double quotes and can't migrate to using double or single quotes.\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for multiline strings and docstrings, regardless of the configured quote style.\n\nRuff may also deviate from using the configured quotes if doing so requires escaping quote characters within the string. For example, given:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change `a` to use single quotes when using `quote-style = \"single\"`. However, `b` remains unchanged, as converting to single quotes requires escaping the inner `'`, which leads to less readable code: `'It\\'s monday morning'`. This does not apply when using `preserve`.", + "description": "Configures the preferred quote character for strings. The recommended options are * `double` (default): Use double quotes `\"` * `single`: Use single quotes `'`\n\nIn compliance with [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/), Ruff prefers double quotes for triple quoted strings and docstrings, regardless of the configured quote style.\n\nRuff deviates from using the configured quotes if doing so prevents the need for escaping quote characters inside the string:\n\n```python a = \"a string without any quotes\" b = \"It's monday morning\" ```\n\nRuff will change the quotes of the string assigned to `a` to single quotes when using `quote-style = \"single\"`. However, ruff uses double quotes for he string assigned to `b` because using single quotes would require escaping the `'`, which leads to the less readable code: `'It\\'s monday morning'`.\n\nIn addition, Ruff supports the quote style `preserve` for projects that already use a mixture of single and double quotes and can't migrate to the `double` or `single` style. The quote style `preserve` leaves the quotes of all strings unchanged. We don't recommend using `preserve` for new project because it doesn't enforce consistent quotes.", "anyOf": [ { "$ref": "#/definitions/QuoteStyle"