mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 04:45:01 +00:00
Add knot.toml schema (#15735)
## Summary Adds a JSON schema generation step for Red Knot. This PR doesn't yet add a publishing step because it's still a bit early for that ## Test plan I tested the schema in Zed, VS Code and PyCharm: * PyCharm: You have to manually add a schema mapping (settings JSON Schema Mappings) * Zed and VS code support the inline schema specification ```toml #:schema /Users/micha/astral/ruff/knot.schema.json [environment] extra-paths = [] [rules] call-possibly-unbound-method = "error" unknown-rule = "error" # duplicate-base = "error" ``` ```json { "$schema": "file:///Users/micha/astral/ruff/knot.schema.json", "environment": { "python-version": "3.13", "python-platform": "linux2" }, "rules": { "unknown-rule": "error" } } ``` https://github.com/user-attachments/assets/a18fcd96-7cbe-4110-985b-9f1935584411 The Schema overall works but all editors have their own quirks: * PyCharm: Hovering a name always shows the section description instead of the description of the specific setting. But it's the same for other settings in `pyproject.toml` files 🤷 * VS Code (JSON): Using the generated schema in a JSON file gives exactly the experience I want * VS Code (TOML): * Properties with multiple possible values are repeated during auto-completion without giving any hint how they're different.  * The property description mushes together the description of the property and the value, which looks sort of ridiculous.  * Autocompletion and documentation hovering works (except the limitations mentioned above) * Zed: * Very similar to VS Code with the exception that it uses the description attribute to distinguish settings with multiple possible values  I don't think there's much we can do here other than hope (or help) editors improve their auto completion. The same short comings also apply to ruff, so this isn't something new. For now, I think this is good enough
This commit is contained in:
parent
7db5a924af
commit
26c37b1e0e
17 changed files with 1087 additions and 96 deletions
|
@ -31,6 +31,20 @@ impl PythonVersion {
|
|||
minor: 13,
|
||||
};
|
||||
|
||||
pub fn iter() -> impl Iterator<Item = PythonVersion> {
|
||||
[
|
||||
PythonVersion::PY37,
|
||||
PythonVersion::PY38,
|
||||
PythonVersion::PY39,
|
||||
PythonVersion::PY310,
|
||||
PythonVersion::PY311,
|
||||
PythonVersion::PY312,
|
||||
PythonVersion::PY313,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub fn free_threaded_build_available(self) -> bool {
|
||||
self >= PythonVersion::PY313
|
||||
}
|
||||
|
@ -69,40 +83,86 @@ impl fmt::Display for PythonVersion {
|
|||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for PythonVersion {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let as_str = String::deserialize(deserializer)?;
|
||||
mod serde {
|
||||
use crate::PythonVersion;
|
||||
|
||||
if let Some((major, minor)) = as_str.split_once('.') {
|
||||
let major = major
|
||||
.parse()
|
||||
.map_err(|err| serde::de::Error::custom(format!("invalid major version: {err}")))?;
|
||||
let minor = minor
|
||||
.parse()
|
||||
.map_err(|err| serde::de::Error::custom(format!("invalid minor version: {err}")))?;
|
||||
impl<'de> serde::Deserialize<'de> for PythonVersion {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let as_str = String::deserialize(deserializer)?;
|
||||
|
||||
Ok((major, minor).into())
|
||||
} else {
|
||||
let major = as_str.parse().map_err(|err| {
|
||||
serde::de::Error::custom(format!(
|
||||
"invalid python-version: {err}, expected: `major.minor`"
|
||||
))
|
||||
})?;
|
||||
if let Some((major, minor)) = as_str.split_once('.') {
|
||||
let major = major.parse().map_err(|err| {
|
||||
serde::de::Error::custom(format!("invalid major version: {err}"))
|
||||
})?;
|
||||
let minor = minor.parse().map_err(|err| {
|
||||
serde::de::Error::custom(format!("invalid minor version: {err}"))
|
||||
})?;
|
||||
|
||||
Ok((major, 0).into())
|
||||
Ok((major, minor).into())
|
||||
} else {
|
||||
let major = as_str.parse().map_err(|err| {
|
||||
serde::de::Error::custom(format!(
|
||||
"invalid python-version: {err}, expected: `major.minor`"
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok((major, 0).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for PythonVersion {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for PythonVersion {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
#[cfg(feature = "schemars")]
|
||||
mod schemars {
|
||||
use super::PythonVersion;
|
||||
use schemars::schema::{Metadata, Schema, SchemaObject, SubschemaValidation};
|
||||
use schemars::JsonSchema;
|
||||
use schemars::_serde_json::Value;
|
||||
|
||||
impl JsonSchema for PythonVersion {
|
||||
fn schema_name() -> String {
|
||||
"PythonVersion".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
let sub_schemas = std::iter::once(Schema::Object(SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
string: Some(Box::new(schemars::schema::StringValidation {
|
||||
pattern: Some(r"^\d+\.\d+$".to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.chain(Self::iter().map(|v| {
|
||||
Schema::Object(SchemaObject {
|
||||
const_value: Some(Value::String(v.to_string())),
|
||||
metadata: Some(Box::new(Metadata {
|
||||
description: Some(format!("Python {v}")),
|
||||
..Metadata::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
}));
|
||||
|
||||
Schema::Object(SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
any_of: Some(sub_schemas.collect()),
|
||||
..Default::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue