diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 4c38249b0e5dc..92c116acb7a24 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -34,7 +34,7 @@ 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, MagicTrailingComma, QuoteStyle}; use crate::options::{ Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, @@ -180,6 +180,9 @@ impl Configuration { magic_trailing_comma: format .magic_trailing_comma .unwrap_or(format_defaults.magic_trailing_comma), + docstring_code: format + .docstring_code + .unwrap_or(format_defaults.docstring_code), }; let lint = self.lint; @@ -1011,6 +1014,7 @@ pub struct FormatConfiguration { pub quote_style: Option, pub magic_trailing_comma: Option, pub line_ending: Option, + pub docstring_code: Option, } impl FormatConfiguration { @@ -1037,6 +1041,13 @@ impl FormatConfiguration { } }), line_ending: options.line_ending, + docstring_code: options.docstring_code.map(|yes| { + if yes { + DocstringCode::Enabled + } else { + DocstringCode::Disabled + } + }), }) } @@ -1050,6 +1061,7 @@ 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: self.docstring_code.or(other.docstring_code), } } } diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 05f4522710cd9..4c4c510c06139 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2871,6 +2871,49 @@ pub struct FormatOptions { "# )] pub line_ending: Option, + + /// 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: + /// + /// >>> 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: + /// + /// >>> f(x) + /// """ + /// pass + /// ``` + /// + /// If a doctest contains invalid Python code or if the formatter would + /// otherwise write invalid Python code, then the doctest is ignored by + /// the formatter and kept as-is. + #[option( + default = "false", + value_type = "bool", + example = r#" + # Enable reformatting of code snippets in docstrings. + docstring-code = true + "# + )] + pub docstring_code: Option, } #[cfg(test)] diff --git a/crates/ruff_workspace/src/settings.rs b/crates/ruff_workspace/src/settings.rs index 982732e487317..4206867abd2ad 100644 --- a/crates/ruff_workspace/src/settings.rs +++ b/crates/ruff_workspace/src/settings.rs @@ -5,7 +5,9 @@ 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, MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle, +}; use ruff_source_file::find_newline; use std::path::{Path, PathBuf}; @@ -124,6 +126,8 @@ pub struct FormatterSettings { pub magic_trailing_comma: MagicTrailingComma, pub line_ending: LineEnding, + + pub docstring_code: DocstringCode, } impl FormatterSettings { @@ -157,6 +161,7 @@ impl FormatterSettings { .with_preview(self.preview) .with_line_ending(line_ending) .with_line_width(self.line_width) + .with_docstring_code(self.docstring_code) } } @@ -173,6 +178,7 @@ 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: default_options.docstring_code(), } } } diff --git a/ruff.schema.json b/ruff.schema.json index cb37f44161c40..f8fe0509c1576 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1241,6 +1241,13 @@ "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": { + "description": "Whether to format code snippets in docstrings.\n\nWhen this is enabled, Python code in the format of doctests within docstrings is automatically reformatted.\n\nFor example, when this is enabled, the following code:\n\n```python def f(x): \"\"\" Something about `f`. And an example:\n\n>>> f( x ) \"\"\" pass ```\n\nwill 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>>> f(x) \"\"\" pass ```\n\nIf a doctest contains invalid Python code or if the formatter would otherwise write invalid Python code, then the doctest is ignored by the formatter and kept as-is.", + "type": [ + "boolean", + "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": [