From 7cac0da44db2f008c41b67c1dce36ce417336873 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 6 Feb 2025 15:00:50 +0000 Subject: [PATCH] Workaround Even Better TOML crash related to `allOf` (#15992) ## Summary Fixes https://github.com/astral-sh/ruff/issues/15978 Even Better TOML doesn't support `allOf` well. In fact, it just crashes. This PR works around this limitation by avoid using `allOf` in the automatically derived schema for the docstring formatting setting. ### Alternatives schemars introduces `allOf` whenver it sees a `$ref` alongside other object properties because this is no longer valid according to Draft 7. We could replace the visitor performing the rewrite but I prefer not to because replacing `allOf` with `oneOf` is only valid for objects that don't have any other `oneOf` or `anyOf` schema. ## Test Plan https://github.com/user-attachments/assets/25d73b2a-fee1-4ba6-9ffe-869b2c3bc64e --- crates/ruff_python_formatter/src/options.rs | 57 ++++++++++++++------- ruff.schema.json | 12 +---- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index 790bcfe0f35c8b..b268bbd59d4f28 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -374,12 +374,15 @@ impl fmt::Display for DocstringCode { } #[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 = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(untagged, rename_all = "lowercase") +)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum DocstringCodeLineWidth { /// Wrap docstring code examples at a fixed line width. + #[cfg_attr(feature = "schemars", schemars(schema_with = "schema::fixed"))] Fixed(LineWidth), /// Respect the line length limit setting for the surrounding Python code. @@ -388,27 +391,45 @@ pub enum DocstringCodeLineWidth { feature = "serde", serde(deserialize_with = "deserialize_docstring_code_line_width_dynamic") )] - #[cfg_attr(feature = "schemars", schemars(with = "DynamicSchema"))] + #[cfg_attr(feature = "schemars", schemars(schema_with = "schema::dynamic"))] Dynamic, } -/// A dummy type that is used to generate a schema for `DocstringCodeLineWidth::Dynamic`. #[cfg(feature = "schemars")] -struct DynamicSchema; - -#[cfg(feature = "schemars")] -impl schemars::JsonSchema for DynamicSchema { - fn schema_name() -> String { - "Dynamic".to_string() - } - - fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), +mod schema { + use ruff_formatter::LineWidth; + use schemars::gen::SchemaGenerator; + use schemars::schema::{Metadata, Schema, SubschemaValidation}; + + /// A dummy type that is used to generate a schema for `DocstringCodeLineWidth::Dynamic`. + pub(super) fn dynamic(_: &mut SchemaGenerator) -> Schema { + Schema::Object(schemars::schema::SchemaObject { const_value: Some("dynamic".to_string().into()), ..Default::default() - } - .into() + }) + } + + // We use a manual schema for `fixed` even thought it isn't strictly necessary according to the + // JSON schema specification to work around a bug in Even Better TOML with `allOf`. + // https://github.com/astral-sh/ruff/issues/15978#issuecomment-2639547101 + // + // The only difference to the automatically derived schema is that we use `oneOf` instead of + // `allOf`. There's no semantic difference between `allOf` and `oneOf` for single element lists. + pub(super) fn fixed(gen: &mut SchemaGenerator) -> Schema { + let schema = gen.subschema_for::(); + Schema::Object(schemars::schema::SchemaObject { + metadata: Some(Box::new(Metadata { + description: Some( + "Wrap docstring code examples at a fixed line width.".to_string(), + ), + ..Metadata::default() + })), + subschemas: Some(Box::new(SubschemaValidation { + one_of: Some(vec![schema]), + ..SubschemaValidation::default() + })), + ..Default::default() + }) } } diff --git a/ruff.schema.json b/ruff.schema.json index 05cecddeb11b3a..fd005ceced23bd 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -879,7 +879,7 @@ "anyOf": [ { "description": "Wrap docstring code examples at a fixed line width.", - "allOf": [ + "oneOf": [ { "$ref": "#/definitions/LineWidth" } @@ -887,18 +887,10 @@ }, { "description": "Respect the line length limit setting for the surrounding Python code.", - "allOf": [ - { - "$ref": "#/definitions/Dynamic" - } - ] + "const": "dynamic" } ] }, - "Dynamic": { - "type": "string", - "const": "dynamic" - }, "Flake8AnnotationsOptions": { "description": "Options for the `flake8-annotations` plugin.", "type": "object",