config: add new docstring-code-format knob (#8854)

This PR does the plumbing to make a new formatting option,
`docstring-code-format`, available in the configuration for end users.
It is disabled by default (opt-in). It is opt-in at least initially to
reflect a conservative posture. The intent is to make it opt-out at some
point in the future.

This was split out from #8811 in order to make #8811 easier to merge.
Namely, once this is merged, docstring code snippet formatting will
become available to end users. (See comments below for how we arrived at
the name.)

Closes #7146

## Test Plan

Other than the standard test suite, I ran the formatter over the CPython
and polars projects to ensure both that the result looked sensible and
that tests still passed. At time of writing, one issue that currently
appears is that reformatting code snippets trips the long line lint:
1905886802
This commit is contained in:
Andrew Gallant 2023-12-13 11:02:11 -05:00 committed by GitHub
parent 18452cf477
commit b6fb972e6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 505 additions and 55 deletions

View file

@ -139,6 +139,99 @@ if condition:
Ok(())
}
#[test]
fn docstring_options() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[format]
docstring-code-format = true
docstring-code-line-length = 20
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["format", "--config"])
.arg(&ruff_toml)
.arg("-")
.pass_stdin(r#"
def f(x):
'''
Something about `f`. And an example:
.. code-block:: python
foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
Another example:
```py
foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
```
And another:
>>> foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
'''
pass
"#), @r###"
success: true
exit_code: 0
----- stdout -----
def f(x):
"""
Something about `f`. And an example:
.. code-block:: python
(
foo,
bar,
quux,
) = this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
Another example:
```py
(
foo,
bar,
quux,
) = this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
```
And another:
>>> (
... foo,
... bar,
... quux,
... ) = this_is_a_long_line(
... lion,
... hippo,
... lemur,
... bear,
... )
"""
pass
----- stderr -----
"###);
Ok(())
}
#[test]
fn mixed_line_endings() -> Result<()> {
let tempdir = TempDir::new()?;

View file

@ -175,6 +175,12 @@ impl PyFormatOptions {
self
}
#[must_use]
pub fn with_docstring_code_line_width(mut self, line_width: DocstringCodeLineWidth) -> Self {
self.docstring_code_line_width = line_width;
self
}
#[must_use]
pub fn with_preview(mut self, preview: PreviewMode) -> Self {
self.preview = preview;
@ -302,13 +308,14 @@ impl DocstringCode {
}
}
#[derive(Copy, Clone, Eq, PartialEq, CacheKey)]
#[derive(Copy, Clone, Default, Eq, PartialEq, CacheKey)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DocstringCodeLineWidth {
Fixed(LineWidth),
#[default]
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "deserialize_docstring_code_line_width_dynamic")
@ -316,12 +323,6 @@ pub enum DocstringCodeLineWidth {
Dynamic,
}
impl Default for DocstringCodeLineWidth {
fn default() -> DocstringCodeLineWidth {
DocstringCodeLineWidth::Fixed(default_line_width())
}
}
impl std::fmt::Debug for DocstringCodeLineWidth {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {

View file

@ -173,7 +173,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -347,7 +347,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -521,7 +521,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -695,7 +695,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -869,7 +869,7 @@ quote-style = Single
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -1366,7 +1366,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -2736,7 +2736,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -4106,7 +4106,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -5476,7 +5476,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -6846,7 +6846,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -7090,7 +7090,9 @@ def doctest_long_lines():
This won't get wrapped even though it exceeds our configured
line width because it doesn't exceed the line width within this
docstring. e.g, the `f` in `foo` is treated as the first column.
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
>>> foo, bar, quux = this_is_a_long_line(
... lion, giraffe, hippo, zeba, lemur, penguin, monkey
... )
But this one is long enough to get wrapped.
>>> foo, bar, quux = this_is_a_long_line(
@ -8211,7 +8213,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -8455,7 +8457,9 @@ def doctest_long_lines():
This won't get wrapped even though it exceeds our configured
line width because it doesn't exceed the line width within this
docstring. e.g, the `f` in `foo` is treated as the first column.
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
>>> foo, bar, quux = this_is_a_long_line(
... lion, giraffe, hippo, zeba, lemur, penguin, monkey
... )
But this one is long enough to get wrapped.
>>> foo, bar, quux = this_is_a_long_line(
@ -9576,7 +9580,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -9820,11 +9824,22 @@ def doctest_long_lines():
This won't get wrapped even though it exceeds our configured
line width because it doesn't exceed the line width within this
docstring. e.g, the `f` in `foo` is treated as the first column.
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
>>> foo, bar, quux = this_is_a_long_line(
... lion, giraffe, hippo, zeba, lemur, penguin, monkey
... )
But this one is long enough to get wrapped.
>>> foo, bar, quux = this_is_a_long_line(
... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard
... lion,
... giraffe,
... hippo,
... zeba,
... lemur,
... penguin,
... monkey,
... spider,
... bear,
... leopard,
... )
"""
# This demostrates a normal line that will get wrapped but won't
@ -10941,7 +10956,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -11185,7 +11200,9 @@ def doctest_long_lines():
This won't get wrapped even though it exceeds our configured
line width because it doesn't exceed the line width within this
docstring. e.g, the `f` in `foo` is treated as the first column.
>>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey)
>>> foo, bar, quux = this_is_a_long_line(
... lion, giraffe, hippo, zeba, lemur, penguin, monkey
... )
But this one is long enough to get wrapped.
>>> foo, bar, quux = this_is_a_long_line(

View file

@ -25,7 +25,7 @@ quote-style = Double
line-ending = CarriageReturnLineFeed
magic-trailing-comma = Respect
docstring-code = Enabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -136,7 +136,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -288,7 +288,7 @@ quote-style = Single
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -151,7 +151,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -327,7 +327,7 @@ quote-style = Single
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -35,7 +35,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -71,7 +71,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -16,7 +16,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -33,7 +33,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -50,7 +50,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -31,7 +31,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -64,7 +64,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -97,7 +97,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -82,7 +82,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -164,7 +164,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Enabled
```

View file

@ -66,7 +66,7 @@ quote-style = Single
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -137,7 +137,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -208,7 +208,7 @@ quote-style = Preserve
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -49,7 +49,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -105,7 +105,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Ignore
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -234,7 +234,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Enabled
```

View file

@ -24,7 +24,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -49,7 +49,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```
@ -77,7 +77,7 @@ quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = 88
docstring-code-line-width = "dynamic"
preview = Disabled
```

View file

@ -34,7 +34,9 @@ use ruff_linter::settings::{
use ruff_linter::{
fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION,
};
use ruff_python_formatter::{MagicTrailingComma, QuoteStyle};
use ruff_python_formatter::{
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, QuoteStyle,
};
use crate::options::{
Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions,
@ -189,6 +191,12 @@ impl Configuration {
magic_trailing_comma: format
.magic_trailing_comma
.unwrap_or(format_defaults.magic_trailing_comma),
docstring_code_format: format
.docstring_code_format
.unwrap_or(format_defaults.docstring_code_format),
docstring_code_line_width: format
.docstring_code_line_width
.unwrap_or(format_defaults.docstring_code_line_width),
};
let lint = self.lint;
@ -1020,6 +1028,8 @@ pub struct FormatConfiguration {
pub quote_style: Option<QuoteStyle>,
pub magic_trailing_comma: Option<MagicTrailingComma>,
pub line_ending: Option<LineEnding>,
pub docstring_code_format: Option<DocstringCode>,
pub docstring_code_line_width: Option<DocstringCodeLineWidth>,
}
impl FormatConfiguration {
@ -1046,6 +1056,14 @@ impl FormatConfiguration {
}
}),
line_ending: options.line_ending,
docstring_code_format: options.docstring_code_format.map(|yes| {
if yes {
DocstringCode::Enabled
} else {
DocstringCode::Disabled
}
}),
docstring_code_line_width: options.docstring_code_line_length,
})
}
@ -1059,6 +1077,10 @@ impl FormatConfiguration {
quote_style: self.quote_style.or(other.quote_style),
magic_trailing_comma: self.magic_trailing_comma.or(other.magic_trailing_comma),
line_ending: self.line_ending.or(other.line_ending),
docstring_code_format: self.docstring_code_format.or(other.docstring_code_format),
docstring_code_line_width: self
.docstring_code_line_width
.or(other.docstring_code_line_width),
}
}
}

View file

@ -27,7 +27,7 @@ use ruff_linter::settings::types::{
};
use ruff_linter::{warn_user_once, RuleSelector};
use ruff_macros::{CombineOptions, OptionsMetadata};
use ruff_python_formatter::QuoteStyle;
use ruff_python_formatter::{DocstringCodeLineWidth, QuoteStyle};
use crate::settings::LineEnding;
@ -2948,6 +2948,156 @@ pub struct FormatOptions {
"#
)]
pub line_ending: Option<LineEnding>,
/// Whether to format code snippets in docstrings.
///
/// When this is enabled, Python code examples within docstrings are
/// automatically reformatted.
///
/// For example, when this is enabled, the following code:
///
/// ```python
/// def f(x):
/// """
/// Something about `f`. And an example in doctest format:
///
/// >>> f( x )
///
/// Markdown is also supported:
///
/// ```py
/// f( x )
/// ```
///
/// As are reStructuredText literal blocks::
///
/// f( x )
///
///
/// And reStructuredText code blocks:
///
/// .. code-block:: python
///
/// f( x )
/// """
/// pass
/// ```
///
/// ... will be reformatted (assuming the rest of the options are set to
/// their defaults) as:
///
/// ```python
/// def f(x):
/// """
/// Something about `f`. And an example in doctest format:
///
/// >>> f(x)
///
/// Markdown is also supported:
///
/// ```py
/// f(x)
/// ```
///
/// As are reStructuredText literal blocks::
///
/// f(x)
///
///
/// And reStructuredText code blocks:
///
/// .. code-block:: python
///
/// f(x)
/// """
/// pass
/// ```
///
/// If a code snippt in a docstring contains invalid Python code or if the
/// formatter would otherwise write invalid Python code, then the code
/// example is ignored by the formatter and kept as-is.
///
/// Currently, doctest, Markdown, reStructuredText literal blocks, and
/// reStructuredText code blocks are all supported and automatically
/// recognized. In the case of unlabeled fenced code blocks in Markdown and
/// reStructuredText literal blocks, the contents are assumed to be Python
/// and reformatted. As with any other format, if the contents aren't valid
/// Python, then the block is left untouched automatically.
#[option(
default = "false",
value_type = "bool",
example = r#"
# Enable reformatting of code snippets in docstrings.
docstring-code-format = true
"#
)]
pub docstring_code_format: Option<bool>,
/// Set the line length used when formatting code snippets in docstrings.
///
/// This only has an effect when the `docstring-code-format` setting is
/// enabled.
///
/// The default value for this setting is `"dynamic"`, which has the effect
/// of ensuring that any reformatted code examples in docstrings adhere to
/// the global line length configuration that is used for the surrounding
/// Python code. The point of this setting is that it takes the indentation
/// of the docstring into account when reformatting code examples.
///
/// Alternatively, this can be set to a fixed integer, which will result
/// in the same line length limit being applied to all reformatted code
/// examples in docstrings. When set to a fixed integer, the indent of the
/// docstring is not taken into account. That is, this may result in lines
/// in the reformatted code example that exceed the globally configured
/// line length limit.
///
/// For example, when this is set to `20` and `docstring-code-format` is
/// enabled, then this code:
///
/// ```python
/// def f(x):
/// '''
/// Something about `f`. And an example:
///
/// .. code-block:: python
///
/// foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
/// '''
/// pass
/// ```
///
/// ... will be reformatted (assuming the rest of the options are set
/// to their defaults) as:
///
/// ```python
/// def f(x):
/// """
/// Something about `f`. And an example:
///
/// .. code-block:: python
///
/// (
/// foo,
/// bar,
/// quux,
/// ) = this_is_a_long_line(
/// lion,
/// hippo,
/// lemur,
/// bear,
/// )
/// """
/// pass
/// ```
#[option(
default = r#""dynamic""#,
value_type = r#"int | "dynamic""#,
example = r#"
# Format all docstring code snippets with a line length of 60.
docstring-code-line-length = 60
"#
)]
pub docstring_code_line_length: Option<DocstringCodeLineWidth>,
}
#[cfg(test)]

View file

@ -5,7 +5,10 @@ use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFor
use ruff_linter::settings::LinterSettings;
use ruff_macros::CacheKey;
use ruff_python_ast::PySourceType;
use ruff_python_formatter::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
use ruff_python_formatter::{
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
QuoteStyle,
};
use ruff_source_file::find_newline;
use std::path::{Path, PathBuf};
@ -124,6 +127,9 @@ pub struct FormatterSettings {
pub magic_trailing_comma: MagicTrailingComma,
pub line_ending: LineEnding,
pub docstring_code_format: DocstringCode,
pub docstring_code_line_width: DocstringCodeLineWidth,
}
impl FormatterSettings {
@ -157,6 +163,8 @@ impl FormatterSettings {
.with_preview(self.preview)
.with_line_ending(line_ending)
.with_line_width(self.line_width)
.with_docstring_code(self.docstring_code_format)
.with_docstring_code_line_width(self.docstring_code_line_width)
}
}
@ -173,6 +181,8 @@ impl Default for FormatterSettings {
indent_width: default_options.indent_width(),
quote_style: default_options.quote_style(),
magic_trailing_comma: default_options.magic_trailing_comma(),
docstring_code_format: default_options.docstring_code(),
docstring_code_line_width: default_options.docstring_code_line_width(),
}
}
}

View file

@ -71,6 +71,20 @@ If left unspecified, Ruff's default configuration is equivalent to:
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
```
=== "ruff.toml"
@ -134,6 +148,20 @@ If left unspecified, Ruff's default configuration is equivalent to:
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
```
As an example, the following would configure Ruff to:

View file

@ -103,10 +103,12 @@ Going forward, the Ruff Formatter will support Black's preview style under Ruff'
## Configuration
The Ruff Formatter exposes a small set of configuration options, some of which are also supported
by Black (like line width), some of which are unique to Ruff (like quote and indentation style).
by Black (like line width), some of which are unique to Ruff (like quote, indentation style and
formatting code examples in docstrings).
For example, to configure the formatter to use single quotes, a line width of 100, and
tab indentation, add the following to your configuration file:
For example, to configure the formatter to use single quotes, format code
examples in docstrings, a line width of 100, and tab indentation, add the
following to your configuration file:
=== "pyproject.toml"
@ -117,6 +119,7 @@ tab indentation, add the following to your configuration file:
[tool.ruff.format]
quote-style = "single"
indent-style = "tab"
docstring-code-format = true
```
=== "ruff.toml"
@ -127,6 +130,7 @@ tab indentation, add the following to your configuration file:
[format]
quote-style = "single"
indent-style = "tab"
docstring-code-format = true
```
@ -137,6 +141,97 @@ Given the focus on Black compatibility (and unlike formatters like [YAPF](https:
Ruff does not currently expose any configuration options to modify core formatting behavior outside
of these trivia-related settings.
## Docstring formatting
The Ruff formatter provides an opt-in feature for automatically formatting
Python code examples in docstrings. The Ruff formatter currently recognizes
code examples in the following formats:
* The Python [doctest] format.
* CommonMark [fenced code blocks] with the following info strings: `python`,
`py`, `python3`, or `py3`. Fenced code blocks without an info string are
assumed to be Python code examples and also formatted.
* reStructuredText [literal blocks]. While literal blocks may contain things
other than Python, this is meant to reflect a long-standing convention in the
Python ecosystem where literal blocks often contain Python code.
* reStructuredText [`code-block` and `sourcecode` directives]. As with
Markdown, the language names recognized for Python are `python`, `py`,
`python3`, or `py3`.
If a code example is recognized and treated as Python, the Ruff formatter will
automatically skip it if the code does not parse as valid Python or if the
reformatted code would produce an invalid Python program.
Users may also configure the line length limit used for reformatting Python
code examples in docstrings. The default is a special value, `dynamic`, which
instructs the formatter to respect the line length limit setting for the
surrounding Python code. The `dynamic` setting ensures that even when code
examples are found inside indented docstrings, the line length limit configured
for the surrounding Python code will not be exceeded. Users may also configure
a fixed line length limit for code examples in docstrings.
For example, this configuration shows how to enable docstring code formatting
with a fixed line length limit:
=== "pyproject.toml"
```toml
[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = 20
```
=== "ruff.toml"
```toml
[format]
docstring-code-format = true
docstring-code-line-length = 20
```
With the above configuration, this code:
```python
def f(x):
'''
Something about `f`. And an example:
.. code-block:: python
foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
'''
pass
```
... will be reformatted (assuming the rest of the options are set
to their defaults) as:
```python
def f(x):
"""
Something about `f`. And an example:
.. code-block:: python
(
foo,
bar,
quux,
) = this_is_a_long_line(
lion,
hippo,
lemur,
bear,
)
"""
pass
```
[doctest]: https://docs.python.org/3/library/doctest.html
[fenced code blocks]: https://spec.commonmark.org/0.30/#fenced-code-blocks
[literal blocks]: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks
[`code-block` and `sourcecode` directives]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block
## Format suppression
Like Black, Ruff supports `# fmt: on`, `# fmt: off`, and `# fmt: skip` pragma comments, which can

34
ruff.schema.json generated
View file

@ -747,6 +747,16 @@
}
]
},
"DocstringCodeLineWidth": {
"anyOf": [
{
"$ref": "#/definitions/LineWidth"
},
{
"type": "null"
}
]
},
"Flake8AnnotationsOptions": {
"type": "object",
"properties": {
@ -1248,6 +1258,24 @@
"description": "Experimental: Configures how `ruff format` formats your code.\n\nPlease provide feedback in [this discussion](https://github.com/astral-sh/ruff/discussions/7310).",
"type": "object",
"properties": {
"docstring-code-format": {
"description": "Whether to format code snippets in docstrings.\n\nWhen this is enabled, Python code examples within docstrings are automatically reformatted.\n\nFor example, when this is enabled, the following code:\n\n```python def f(x): \"\"\" Something about `f`. And an example in doctest format:\n\n>>> f( x )\n\nMarkdown is also supported:\n\n```py f( x ) ```\n\nAs are reStructuredText literal blocks::\n\nf( x )\n\nAnd reStructuredText code blocks:\n\n.. code-block:: python\n\nf( x ) \"\"\" pass ```\n\n... will be reformatted (assuming the rest of the options are set to their defaults) as:\n\n```python def f(x): \"\"\" Something about `f`. And an example in doctest format:\n\n>>> f(x)\n\nMarkdown is also supported:\n\n```py f(x) ```\n\nAs are reStructuredText literal blocks::\n\nf(x)\n\nAnd reStructuredText code blocks:\n\n.. code-block:: python\n\nf(x) \"\"\" pass ```\n\nIf a code snippt in a docstring contains invalid Python code or if the formatter would otherwise write invalid Python code, then the code example is ignored by the formatter and kept as-is.\n\nCurrently, doctest, Markdown, reStructuredText literal blocks, and reStructuredText code blocks are all supported and automatically recognized. In the case of unlabeled fenced code blocks in Markdown and reStructuredText literal blocks, the contents are assumed to be Python and reformatted. As with any other format, if the contents aren't valid Python, then the block is left untouched automatically.",
"type": [
"boolean",
"null"
]
},
"docstring-code-line-length": {
"description": "Set the line length used when formatting code snippets in docstrings.\n\nThis only has an effect when the `docstring-code-format` setting is enabled.\n\nThe default value for this setting is `\"dynamic\"`, which has the effect of ensuring that any reformatted code examples in docstrings adhere to the global line length configuration that is used for the surrounding Python code. The point of this setting is that it takes the indentation of the docstring into account when reformatting code examples.\n\nAlternatively, this can be set to a fixed integer, which will result in the same line length limit being applied to all reformatted code examples in docstrings. When set to a fixed integer, the indent of the docstring is not taken into account. That is, this may result in lines in the reformatted code example that exceed the globally configured line length limit.\n\nFor example, when this is set to `20` and `docstring-code-format` is enabled, then this code:\n\n```python def f(x): ''' Something about `f`. And an example:\n\n.. code-block:: python\n\nfoo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear) ''' pass ```\n\n... will be reformatted (assuming the rest of the options are set to their defaults) as:\n\n```python def f(x): \"\"\" Something about `f`. And an example:\n\n.. code-block:: python\n\n( foo, bar, quux, ) = this_is_a_long_line( lion, hippo, lemur, bear, ) \"\"\" pass ```",
"anyOf": [
{
"$ref": "#/definitions/DocstringCodeLineWidth"
},
{
"type": "null"
}
]
},
"exclude": {
"description": "A list of file patterns to exclude from formatting in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).\n\nExclusions are based on globs, and can be either:\n\n- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ). - Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py` (to exclude any Python files in `directory`). Note that these paths are relative to the project root (e.g., the directory containing your `pyproject.toml`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
"type": [
@ -1652,6 +1680,12 @@
"maximum": 320.0,
"minimum": 1.0
},
"LineWidth": {
"description": "The maximum visual width to which the formatter should try to limit a line.",
"type": "integer",
"format": "uint16",
"minimum": 1.0
},
"LintOptions": {
"description": "Experimental section to configure Ruff's linting. This new section will eventually replace the top-level linting options.\n\nOptions specified in the `lint` section take precedence over the top-level settings.",
"type": "object",