Skip to content

Commit cd68e6c

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 8d4b010 commit cd68e6c

File tree

8 files changed

+444
-6
lines changed

8 files changed

+444
-6
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

+6
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;

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

+151-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,156 @@ 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 examples within docstrings are
2903+
/// 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 to
2935+
/// 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. In the case of unlabeled fenced code blocks in Markdown and
2971+
/// reStructuredText literal blocks, the contents are assumed to be Python
2972+
/// and reformatted. As with any other format, if the contents aren't valid
2973+
/// Python, then the block is left untouched automatically.
2974+
#[option(
2975+
default = "false",
2976+
value_type = "bool",
2977+
example = r#"
2978+
# Enable reformatting of code snippets in docstrings.
2979+
docstring-code-format = true
2980+
"#
2981+
)]
2982+
pub docstring_code_format: Option<bool>,
2983+
2984+
/// Set the line length used when formatting code snippets in docstrings.
2985+
///
2986+
/// This only has an effect when the `docstring-code-format` setting is
2987+
/// enabled.
2988+
///
2989+
/// The default value for this setting is `"dynamic"`, which has the effect
2990+
/// of ensuring that any reformatted code examples in docstrings adhere to
2991+
/// the global line length configuration that is used for the surrounding
2992+
/// Python code. The point of this setting is that it takes the indentation
2993+
/// of the docstring into account when reformatting code examples.
2994+
///
2995+
/// Alternatively, this can be set to a fixed integer, which will result
2996+
/// in the same line length limit being applied to all reformatted code
2997+
/// examples in docstrings. When set to a fixed integer, the indent of the
2998+
/// docstring is not taken into account. That is, this may result in lines
2999+
/// in the reformatted code example that exceed the globally configured
3000+
/// line length limit.
3001+
///
3002+
/// For example, when this is set to `20` and `docstring-code-format` is
3003+
/// enabled, then this code:
3004+
///
3005+
/// ```python
3006+
/// def f(x):
3007+
/// '''
3008+
/// Something about `f`. And an example:
3009+
///
3010+
/// .. code-block:: python
3011+
///
3012+
/// foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
3013+
/// '''
3014+
/// pass
3015+
/// ```
3016+
///
3017+
/// ... will be reformatted (assuming the rest of the options are set
3018+
/// to their defaults) as:
3019+
///
3020+
/// ```python
3021+
/// def f(x):
3022+
/// """
3023+
/// Something about `f`. And an example:
3024+
///
3025+
/// .. code-block:: python
3026+
///
3027+
/// (
3028+
/// foo,
3029+
/// bar,
3030+
/// quux,
3031+
/// ) = this_is_a_long_line(
3032+
/// lion,
3033+
/// hippo,
3034+
/// lemur,
3035+
/// bear,
3036+
/// )
3037+
/// """
3038+
/// pass
3039+
/// ```
3040+
#[option(
3041+
default = r#""dynamic""#,
3042+
value_type = r#"int | "dynamic""#,
3043+
example = r#"
3044+
# Format all docstring code snippets with a line length of 60.
3045+
docstring-code-line-length = 60
3046+
"#
3047+
)]
3048+
pub docstring_code_line_length: Option<DocstringCodeLineWidth>,
28993049
}
29003050

29013051
#[cfg(test)]

crates/ruff_workspace/src/settings.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFor
55
use ruff_linter::settings::LinterSettings;
66
use ruff_macros::CacheKey;
77
use ruff_python_ast::PySourceType;
8-
use ruff_python_formatter::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
8+
use ruff_python_formatter::{
9+
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
10+
QuoteStyle,
11+
};
912
use ruff_source_file::find_newline;
1013
use std::path::{Path, PathBuf};
1114

@@ -124,6 +127,9 @@ pub struct FormatterSettings {
124127
pub magic_trailing_comma: MagicTrailingComma,
125128

126129
pub line_ending: LineEnding,
130+
131+
pub docstring_code_format: DocstringCode,
132+
pub docstring_code_line_width: DocstringCodeLineWidth,
127133
}
128134

129135
impl FormatterSettings {
@@ -157,6 +163,8 @@ impl FormatterSettings {
157163
.with_preview(self.preview)
158164
.with_line_ending(line_ending)
159165
.with_line_width(self.line_width)
166+
.with_docstring_code(self.docstring_code_format)
167+
.with_docstring_code_line_width(self.docstring_code_line_width)
160168
}
161169
}
162170

@@ -173,6 +181,8 @@ impl Default for FormatterSettings {
173181
indent_width: default_options.indent_width(),
174182
quote_style: default_options.quote_style(),
175183
magic_trailing_comma: default_options.magic_trailing_comma(),
184+
docstring_code_format: default_options.docstring_code(),
185+
docstring_code_line_width: default_options.docstring_code_line_width(),
176186
}
177187
}
178188
}

0 commit comments

Comments
 (0)