Skip to content

Commit

Permalink
config: add new 'docstring-code-format' and 'docstring-code-line-leng…
Browse files Browse the repository at this point in the history
…th' knobs

This commit 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 also adds a compansion option, 'docstring-code-line-length', that
permits setting a line length for reformatted code examples that is
distinct from the global setting. Its default value is 'dynamic', which
means reformatted code will respect the global line width, regardless of
the indent level of the enclosing docstring.
  • Loading branch information
BurntSushi committed Dec 12, 2023
1 parent b972455 commit 5405750
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 10 deletions.
93 changes: 93 additions & 0 deletions crates/ruff_cli/tests/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down
15 changes: 8 additions & 7 deletions crates/ruff_python_formatter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -302,26 +308,21 @@ 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")
)]
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 {
Expand Down
24 changes: 23 additions & 1 deletion crates/ruff_workspace/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
})
}

Expand All @@ -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),
}
}
}
Expand Down
150 changes: 149 additions & 1 deletion crates/ruff_workspace/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -2896,6 +2896,154 @@ pub struct FormatOptions {
"#
)]
pub line_ending: Option<LineEnding>,

/// Whether to format code snippets in docstrings.
///
/// When this is enabled, Python code in the format of doctests
/// within docstrings is 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.
#[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"`. This setting 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)]
Expand Down

0 comments on commit 5405750

Please sign in to comment.