Skip to content

Commit 5405750

Browse files
committed
config: add new 'docstring-code-format' and 'docstring-code-line-length' 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.
1 parent b972455 commit 5405750

File tree

7 files changed

+346
-10
lines changed

7 files changed

+346
-10
lines changed

crates/ruff_cli/tests/format.rs

+93
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,99 @@ if condition:
139139
Ok(())
140140
}
141141

142+
#[test]
143+
fn docstring_options() -> Result<()> {
144+
let tempdir = TempDir::new()?;
145+
let ruff_toml = tempdir.path().join("ruff.toml");
146+
fs::write(
147+
&ruff_toml,
148+
r#"
149+
[format]
150+
docstring-code-format = true
151+
docstring-code-line-length = 20
152+
"#,
153+
)?;
154+
155+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
156+
.args(["format", "--config"])
157+
.arg(&ruff_toml)
158+
.arg("-")
159+
.pass_stdin(r#"
160+
def f(x):
161+
'''
162+
Something about `f`. And an example:
163+
164+
.. code-block:: python
165+
166+
foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
167+
168+
Another example:
169+
170+
```py
171+
foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
172+
```
173+
174+
And another:
175+
176+
>>> foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
177+
'''
178+
pass
179+
"#), @r###"
180+
success: true
181+
exit_code: 0
182+
----- stdout -----
183+
def f(x):
184+
"""
185+
Something about `f`. And an example:
186+
187+
.. code-block:: python
188+
189+
(
190+
foo,
191+
bar,
192+
quux,
193+
) = this_is_a_long_line(
194+
lion,
195+
hippo,
196+
lemur,
197+
bear,
198+
)
199+
200+
Another example:
201+
202+
```py
203+
(
204+
foo,
205+
bar,
206+
quux,
207+
) = this_is_a_long_line(
208+
lion,
209+
hippo,
210+
lemur,
211+
bear,
212+
)
213+
```
214+
215+
And another:
216+
217+
>>> (
218+
... foo,
219+
... bar,
220+
... quux,
221+
... ) = this_is_a_long_line(
222+
... lion,
223+
... hippo,
224+
... lemur,
225+
... bear,
226+
... )
227+
"""
228+
pass
229+
230+
----- stderr -----
231+
"###);
232+
Ok(())
233+
}
234+
142235
#[test]
143236
fn mixed_line_endings() -> Result<()> {
144237
let tempdir = TempDir::new()?;

crates/ruff_python_formatter/src/options.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ impl PyFormatOptions {
175175
self
176176
}
177177

178+
#[must_use]
179+
pub fn with_docstring_code_line_width(mut self, line_width: DocstringCodeLineWidth) -> Self {
180+
self.docstring_code_line_width = line_width;
181+
self
182+
}
183+
178184
#[must_use]
179185
pub fn with_preview(mut self, preview: PreviewMode) -> Self {
180186
self.preview = preview;
@@ -302,26 +308,21 @@ impl DocstringCode {
302308
}
303309
}
304310

305-
#[derive(Copy, Clone, Eq, PartialEq, CacheKey)]
311+
#[derive(Copy, Clone, Default, Eq, PartialEq, CacheKey)]
306312
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
307313
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
308314
#[cfg_attr(feature = "serde", serde(untagged))]
309315
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
310316
pub enum DocstringCodeLineWidth {
311317
Fixed(LineWidth),
318+
#[default]
312319
#[cfg_attr(
313320
feature = "serde",
314321
serde(deserialize_with = "deserialize_docstring_code_line_width_dynamic")
315322
)]
316323
Dynamic,
317324
}
318325

319-
impl Default for DocstringCodeLineWidth {
320-
fn default() -> DocstringCodeLineWidth {
321-
DocstringCodeLineWidth::Fixed(default_line_width())
322-
}
323-
}
324-
325326
impl std::fmt::Debug for DocstringCodeLineWidth {
326327
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
327328
match *self {

crates/ruff_workspace/src/configuration.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ use ruff_linter::settings::{
3434
use ruff_linter::{
3535
fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION,
3636
};
37-
use ruff_python_formatter::{MagicTrailingComma, QuoteStyle};
37+
use ruff_python_formatter::{
38+
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, QuoteStyle,
39+
};
3840

3941
use crate::options::{
4042
Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions,
@@ -189,6 +191,12 @@ impl Configuration {
189191
magic_trailing_comma: format
190192
.magic_trailing_comma
191193
.unwrap_or(format_defaults.magic_trailing_comma),
194+
docstring_code_format: format
195+
.docstring_code_format
196+
.unwrap_or(format_defaults.docstring_code_format),
197+
docstring_code_line_width: format
198+
.docstring_code_line_width
199+
.unwrap_or(format_defaults.docstring_code_line_width),
192200
};
193201

194202
let lint = self.lint;
@@ -1020,6 +1028,8 @@ pub struct FormatConfiguration {
10201028
pub quote_style: Option<QuoteStyle>,
10211029
pub magic_trailing_comma: Option<MagicTrailingComma>,
10221030
pub line_ending: Option<LineEnding>,
1031+
pub docstring_code_format: Option<DocstringCode>,
1032+
pub docstring_code_line_width: Option<DocstringCodeLineWidth>,
10231033
}
10241034

10251035
impl FormatConfiguration {
@@ -1046,6 +1056,14 @@ impl FormatConfiguration {
10461056
}
10471057
}),
10481058
line_ending: options.line_ending,
1059+
docstring_code_format: options.docstring_code_format.map(|yes| {
1060+
if yes {
1061+
DocstringCode::Enabled
1062+
} else {
1063+
DocstringCode::Disabled
1064+
}
1065+
}),
1066+
docstring_code_line_width: options.docstring_code_line_length,
10491067
})
10501068
}
10511069

@@ -1059,6 +1077,10 @@ impl FormatConfiguration {
10591077
quote_style: self.quote_style.or(other.quote_style),
10601078
magic_trailing_comma: self.magic_trailing_comma.or(other.magic_trailing_comma),
10611079
line_ending: self.line_ending.or(other.line_ending),
1080+
docstring_code_format: self.docstring_code_format.or(other.docstring_code_format),
1081+
docstring_code_line_width: self
1082+
.docstring_code_line_width
1083+
.or(other.docstring_code_line_width),
10621084
}
10631085
}
10641086
}

crates/ruff_workspace/src/options.rs

+149-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use ruff_linter::settings::types::{
2727
};
2828
use ruff_linter::{warn_user_once, RuleSelector};
2929
use ruff_macros::{CombineOptions, OptionsMetadata};
30-
use ruff_python_formatter::QuoteStyle;
30+
use ruff_python_formatter::{DocstringCodeLineWidth, QuoteStyle};
3131

3232
use crate::settings::LineEnding;
3333

@@ -2896,6 +2896,154 @@ pub struct FormatOptions {
28962896
"#
28972897
)]
28982898
pub line_ending: Option<LineEnding>,
2899+
2900+
/// Whether to format code snippets in docstrings.
2901+
///
2902+
/// When this is enabled, Python code in the format of doctests
2903+
/// within docstrings is automatically reformatted.
2904+
///
2905+
/// For example, when this is enabled, the following code:
2906+
///
2907+
/// ```python
2908+
/// def f(x):
2909+
/// """
2910+
/// Something about `f`. And an example in doctest format:
2911+
///
2912+
/// >>> f( x )
2913+
///
2914+
/// Markdown is also supported:
2915+
///
2916+
/// ```py
2917+
/// f( x )
2918+
/// ```
2919+
///
2920+
/// As are reStructuredText literal blocks::
2921+
///
2922+
/// f( x )
2923+
///
2924+
///
2925+
/// And reStructuredText code blocks:
2926+
///
2927+
/// .. code-block:: python
2928+
///
2929+
/// f( x )
2930+
/// """
2931+
/// pass
2932+
/// ```
2933+
///
2934+
/// will be reformatted (assuming the rest of the options are set
2935+
/// to their defaults) as:
2936+
///
2937+
/// ```python
2938+
/// def f(x):
2939+
/// """
2940+
/// Something about `f`. And an example in doctest format:
2941+
///
2942+
/// >>> f(x)
2943+
///
2944+
/// Markdown is also supported:
2945+
///
2946+
/// ```py
2947+
/// f(x)
2948+
/// ```
2949+
///
2950+
/// As are reStructuredText literal blocks::
2951+
///
2952+
/// f(x)
2953+
///
2954+
///
2955+
/// And reStructuredText code blocks:
2956+
///
2957+
/// .. code-block:: python
2958+
///
2959+
/// f(x)
2960+
/// """
2961+
/// pass
2962+
/// ```
2963+
///
2964+
/// If a code snippt in a docstring contains invalid Python code or if the
2965+
/// formatter would otherwise write invalid Python code, then the code
2966+
/// example is ignored by the formatter and kept as-is.
2967+
///
2968+
/// Currently, doctest, Markdown, reStructuredText literal blocks and
2969+
/// reStructuredText code blocks are all supported and automatically
2970+
/// recognized.
2971+
#[option(
2972+
default = "false",
2973+
value_type = "bool",
2974+
example = r#"
2975+
# Enable reformatting of code snippets in docstrings.
2976+
docstring-code-format = true
2977+
"#
2978+
)]
2979+
pub docstring_code_format: Option<bool>,
2980+
2981+
/// Set the line length used when formatting code snippets in docstrings.
2982+
///
2983+
/// This only has an effect when the `docstring-code-format` setting is
2984+
/// enabled.
2985+
///
2986+
/// The default value for this setting is `"dynamic"`. This setting has the
2987+
/// effect of ensuring that any reformatted code examples in docstrings
2988+
/// adhere to the global line length configuration that is used for the
2989+
/// surrounding Python code. The point of this setting is that it takes
2990+
/// the indentation of the docstring into account when reformatting code
2991+
/// examples.
2992+
///
2993+
/// Alternatively, this can be set to a fixed integer, which will result
2994+
/// in the same line length limit being applied to all reformatted code
2995+
/// examples in docstrings. When set to a fixed integer, the indent of the
2996+
/// docstring is not taken into account. That is, this may result in lines
2997+
/// in the reformatted code example that exceed the globally configured
2998+
/// line length limit.
2999+
///
3000+
/// For example, when this is set to `20` and `docstring-code-format` is
3001+
/// enabled, then this code:
3002+
///
3003+
/// ```python
3004+
/// def f(x):
3005+
/// '''
3006+
/// Something about `f`. And an example:
3007+
///
3008+
/// .. code-block:: python
3009+
///
3010+
/// foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
3011+
/// '''
3012+
/// pass
3013+
/// ```
3014+
///
3015+
/// will be reformatted (assuming the rest of the options are set
3016+
/// to their defaults) as:
3017+
///
3018+
/// ```python
3019+
/// def f(x):
3020+
/// """
3021+
/// Something about `f`. And an example:
3022+
///
3023+
/// .. code-block:: python
3024+
///
3025+
/// (
3026+
/// foo,
3027+
/// bar,
3028+
/// quux,
3029+
/// ) = this_is_a_long_line(
3030+
/// lion,
3031+
/// hippo,
3032+
/// lemur,
3033+
/// bear,
3034+
/// )
3035+
/// """
3036+
/// pass
3037+
/// ```
3038+
#[option(
3039+
default = r#""dynamic""#,
3040+
value_type = r#"int | "dynamic""#,
3041+
example = r#"
3042+
# Format all docstring code snippets with a line length of 60.
3043+
docstring-code-line-length = 60
3044+
"#
3045+
)]
3046+
pub docstring_code_line_length: Option<DocstringCodeLineWidth>,
28993047
}
29003048

29013049
#[cfg(test)]

0 commit comments

Comments
 (0)