Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 73 additions & 18 deletions crates/oxc_formatter/src/service/oxfmtrc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::Path;

use schemars::JsonSchema;
use schemars::{JsonSchema, schema_for};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

Expand All @@ -17,47 +17,48 @@ use crate::{
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct Oxfmtrc {
/// Use tabs for indentation or spaces. (Default: false)
/// Use tabs for indentation or spaces. (Default: `false`)
#[serde(skip_serializing_if = "Option::is_none")]
pub use_tabs: Option<bool>,
/// Number of spaces per indentation level. (Default: 2)
/// Number of spaces per indentation level. (Default: `2`)
#[serde(skip_serializing_if = "Option::is_none")]
pub tab_width: Option<u8>,
/// Which end of line characters to apply. (Default: "lf")
/// Which end of line characters to apply. (Default: `"lf"`)
#[serde(skip_serializing_if = "Option::is_none")]
pub end_of_line: Option<EndOfLineConfig>,
/// The line length that the printer will wrap on. (Default: 100)
/// The line length that the printer will wrap on. (Default: `100`)
#[serde(skip_serializing_if = "Option::is_none")]
pub print_width: Option<u16>,
/// Use single quotes instead of double quotes. (Default: false)
/// Use single quotes instead of double quotes. (Default: `false`)
#[serde(skip_serializing_if = "Option::is_none")]
pub single_quote: Option<bool>,
/// Use single quotes instead of double quotes in JSX. (Default: false)
/// Use single quotes instead of double quotes in JSX. (Default: `false`)
#[serde(skip_serializing_if = "Option::is_none")]
pub jsx_single_quote: Option<bool>,
/// Change when properties in objects are quoted. (Default: "as-needed")
/// Change when properties in objects are quoted. (Default: `"as-needed"`)
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_props: Option<QuotePropsConfig>,
/// Print trailing commas wherever possible. (Default: "all")
/// Print trailing commas wherever possible. (Default: `"all"`)
#[serde(skip_serializing_if = "Option::is_none")]
pub trailing_comma: Option<TrailingCommaConfig>,
/// Print semicolons at the ends of statements. (Default: true)
/// Print semicolons at the ends of statements. (Default: `true`)
#[serde(skip_serializing_if = "Option::is_none")]
pub semi: Option<bool>,
/// Include parentheses around a sole arrow function parameter. (Default: "always")
/// Include parentheses around a sole arrow function parameter. (Default: `"always"`)
#[serde(skip_serializing_if = "Option::is_none")]
pub arrow_parens: Option<ArrowParensConfig>,
/// Print spaces between brackets in object literals. (Default: true)
/// Print spaces between brackets in object literals. (Default: `true`)
#[serde(skip_serializing_if = "Option::is_none")]
pub bracket_spacing: Option<bool>,
/// Put the > of a multi-line JSX element at the end of the last line instead of being alone on the next line. (Default: false)
/// Put the `>` of a multi-line JSX element at the end of the last line
/// instead of being alone on the next line. (Default: `false`)
#[serde(skip_serializing_if = "Option::is_none")]
pub bracket_same_line: Option<bool>,
/// How to wrap object literals when they could fit on one line or span multiple lines. (Default: "preserve")
/// NOTE: In addition to Prettier's "preserve" and "collapse", we also support "always".
/// How to wrap object literals when they could fit on one line or span multiple lines. (Default: `"preserve"`)
/// NOTE: In addition to Prettier's `"preserve"` and `"collapse"`, we also support `"always"`.
#[serde(skip_serializing_if = "Option::is_none")]
pub object_wrap: Option<ObjectWrapConfig>,
/// Put each attribute on a new line in JSX. (Default: false)
/// Put each attribute on a new line in JSX. (Default: `false`)
#[serde(skip_serializing_if = "Option::is_none")]
pub single_attribute_per_line: Option<bool>,

Expand All @@ -70,15 +71,15 @@ pub struct Oxfmtrc {
#[schemars(skip)]
pub experimental_ternaries: Option<serde_json::Value>,

/// Control whether formats quoted code embedded in the file. (Default: "auto")
/// Control whether formats quoted code embedded in the file. (Default: `"auto"`)
#[serde(skip_serializing_if = "Option::is_none")]
pub embedded_language_formatting: Option<EmbeddedLanguageFormattingConfig>,

/// Experimental: Sort import statements. Disabled by default.
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental_sort_imports: Option<SortImportsConfig>,

/// Experimental: Sort `package.json` keys. (Default: true)
/// Experimental: Sort `package.json` keys. (Default: `true`)
#[serde(default = "default_true")]
pub experimental_sort_package_json: bool,

Expand Down Expand Up @@ -531,6 +532,60 @@ impl Oxfmtrc {
// e.g. `plugins`, `htmlWhitespaceSensitivity`, `vueIndentScriptAndStyle`, etc.
// Other options defined independently by plugins are also left as they are.
}

/// Generates the JSON schema for Oxfmtrc configuration files.
///
/// # Panics
/// Panics if the schema generation fails.
pub fn generate_schema_json() -> String {
let mut schema = schema_for!(Oxfmtrc);

// Allow comments and trailing commas for vscode-json-languageservice
// NOTE: This is NOT part of standard JSON Schema specification
// https://github.com/microsoft/vscode-json-languageservice/blob/fb83547762901f32d8449d57e24666573016b10c/src/jsonLanguageTypes.ts#L151-L159
schema.schema.extensions.insert("allowComments".to_string(), serde_json::Value::Bool(true));
schema
.schema
.extensions
.insert("allowTrailingCommas".to_string(), serde_json::Value::Bool(true));

// Inject markdownDescription fields for better editor support (e.g., VS Code)
let mut json = serde_json::to_value(&schema).unwrap();
Self::inject_markdown_descriptions(&mut json);

serde_json::to_string_pretty(&json).unwrap()
}

/// Recursively inject `markdownDescription` fields into the JSON schema.
/// This is a non-standard field that some editors (like VS Code) use to render
/// markdown in hover tooltips.
fn inject_markdown_descriptions(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(map) => {
// If this object has a `description` field, copy it to `markdownDescription`
if let Some(serde_json::Value::String(desc_str)) = map.get("description") {
map.insert(
"markdownDescription".to_string(),
serde_json::Value::String(desc_str.clone()),
);
}

// Recursively process all values in the object
for value in map.values_mut() {
Self::inject_markdown_descriptions(value);
}
}
serde_json::Value::Array(items) => {
// Recursively process all items in the array
for item in items {
Self::inject_markdown_descriptions(item);
}
}
_ => {
// Primitive values don't need processing
}
}
}
}

// ---
Expand Down
11 changes: 1 addition & 10 deletions crates/oxc_formatter/tests/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,7 @@ use project_root::get_project_root;
#[test]
fn test_schema_json() {
let path = get_project_root().unwrap().join("npm/oxfmt/configuration_schema.json");
let mut schema = schemars::schema_for!(Oxfmtrc);
// Allow comments and trailing commas for vscode-json-languageservice
// NOTE: This is NOT part of standard JSON Schema specification
// https://github.com/microsoft/vscode-json-languageservice/blob/fb83547762901f32d8449d57e24666573016b10c/src/jsonLanguageTypes.ts#L151-L159
schema.schema.extensions.insert("allowComments".to_string(), serde_json::Value::Bool(true));
schema
.schema
.extensions
.insert("allowTrailingCommas".to_string(), serde_json::Value::Bool(true));
let json = serde_json::to_string_pretty(&schema).unwrap();
let json = Oxfmtrc::generate_schema_json();
let existing_json = fs::read_to_string(&path).unwrap_or_default();
if existing_json.trim() != json.trim() {
std::fs::write(&path, &json).unwrap();
Expand Down
92 changes: 56 additions & 36 deletions crates/oxc_formatter/tests/snapshots/schema_json.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,56 @@ expression: json
"type": "object",
"properties": {
"arrowParens": {
"description": "Include parentheses around a sole arrow function parameter. (Default: \"always\")",
"description": "Include parentheses around a sole arrow function parameter. (Default: `\"always\"`)",
"anyOf": [
{
"$ref": "#/definitions/ArrowParensConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "Include parentheses around a sole arrow function parameter. (Default: `\"always\"`)"
},
"bracketSameLine": {
"description": "Put the > of a multi-line JSX element at the end of the last line instead of being alone on the next line. (Default: false)",
"description": "Put the `>` of a multi-line JSX element at the end of the last line\ninstead of being alone on the next line. (Default: `false`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Put the `>` of a multi-line JSX element at the end of the last line\ninstead of being alone on the next line. (Default: `false`)"
},
"bracketSpacing": {
"description": "Print spaces between brackets in object literals. (Default: true)",
"description": "Print spaces between brackets in object literals. (Default: `true`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Print spaces between brackets in object literals. (Default: `true`)"
},
"embeddedLanguageFormatting": {
"description": "Control whether formats quoted code embedded in the file. (Default: \"auto\")",
"description": "Control whether formats quoted code embedded in the file. (Default: `\"auto\"`)",
"anyOf": [
{
"$ref": "#/definitions/EmbeddedLanguageFormattingConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "Control whether formats quoted code embedded in the file. (Default: `\"auto\"`)"
},
"endOfLine": {
"description": "Which end of line characters to apply. (Default: \"lf\")",
"description": "Which end of line characters to apply. (Default: `\"lf\"`)",
"anyOf": [
{
"$ref": "#/definitions/EndOfLineConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "Which end of line characters to apply. (Default: `\"lf\"`)"
},
"experimentalSortImports": {
"description": "Experimental: Sort import statements. Disabled by default.",
Expand All @@ -64,12 +69,14 @@ expression: json
{
"type": "null"
}
]
],
"markdownDescription": "Experimental: Sort import statements. Disabled by default."
},
"experimentalSortPackageJson": {
"description": "Experimental: Sort `package.json` keys. (Default: true)",
"description": "Experimental: Sort `package.json` keys. (Default: `true`)",
"default": true,
"type": "boolean"
"type": "boolean",
"markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)"
},
"ignorePatterns": {
"description": "Ignore files matching these glob patterns. Current working directory is used as the root.",
Expand All @@ -79,93 +86,104 @@ expression: json
],
"items": {
"type": "string"
}
},
"markdownDescription": "Ignore files matching these glob patterns. Current working directory is used as the root."
},
"jsxSingleQuote": {
"description": "Use single quotes instead of double quotes in JSX. (Default: false)",
"description": "Use single quotes instead of double quotes in JSX. (Default: `false`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Use single quotes instead of double quotes in JSX. (Default: `false`)"
},
"objectWrap": {
"description": "How to wrap object literals when they could fit on one line or span multiple lines. (Default: \"preserve\")\nNOTE: In addition to Prettier's \"preserve\" and \"collapse\", we also support \"always\".",
"description": "How to wrap object literals when they could fit on one line or span multiple lines. (Default: `\"preserve\"`)\nNOTE: In addition to Prettier's `\"preserve\"` and `\"collapse\"`, we also support `\"always\"`.",
"anyOf": [
{
"$ref": "#/definitions/ObjectWrapConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "How to wrap object literals when they could fit on one line or span multiple lines. (Default: `\"preserve\"`)\nNOTE: In addition to Prettier's `\"preserve\"` and `\"collapse\"`, we also support `\"always\"`."
},
"printWidth": {
"description": "The line length that the printer will wrap on. (Default: 100)",
"description": "The line length that the printer will wrap on. (Default: `100`)",
"type": [
"integer",
"null"
],
"format": "uint16",
"minimum": 0.0
"minimum": 0.0,
"markdownDescription": "The line length that the printer will wrap on. (Default: `100`)"
},
"quoteProps": {
"description": "Change when properties in objects are quoted. (Default: \"as-needed\")",
"description": "Change when properties in objects are quoted. (Default: `\"as-needed\"`)",
"anyOf": [
{
"$ref": "#/definitions/QuotePropsConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "Change when properties in objects are quoted. (Default: `\"as-needed\"`)"
},
"semi": {
"description": "Print semicolons at the ends of statements. (Default: true)",
"description": "Print semicolons at the ends of statements. (Default: `true`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Print semicolons at the ends of statements. (Default: `true`)"
},
"singleAttributePerLine": {
"description": "Put each attribute on a new line in JSX. (Default: false)",
"description": "Put each attribute on a new line in JSX. (Default: `false`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Put each attribute on a new line in JSX. (Default: `false`)"
},
"singleQuote": {
"description": "Use single quotes instead of double quotes. (Default: false)",
"description": "Use single quotes instead of double quotes. (Default: `false`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Use single quotes instead of double quotes. (Default: `false`)"
},
"tabWidth": {
"description": "Number of spaces per indentation level. (Default: 2)",
"description": "Number of spaces per indentation level. (Default: `2`)",
"type": [
"integer",
"null"
],
"format": "uint8",
"minimum": 0.0
"minimum": 0.0,
"markdownDescription": "Number of spaces per indentation level. (Default: `2`)"
},
"trailingComma": {
"description": "Print trailing commas wherever possible. (Default: \"all\")",
"description": "Print trailing commas wherever possible. (Default: `\"all\"`)",
"anyOf": [
{
"$ref": "#/definitions/TrailingCommaConfig"
},
{
"type": "null"
}
]
],
"markdownDescription": "Print trailing commas wherever possible. (Default: `\"all\"`)"
},
"useTabs": {
"description": "Use tabs for indentation or spaces. (Default: false)",
"description": "Use tabs for indentation or spaces. (Default: `false`)",
"type": [
"boolean",
"null"
]
],
"markdownDescription": "Use tabs for indentation or spaces. (Default: `false`)"
}
},
"allowComments": true,
Expand Down Expand Up @@ -222,7 +240,8 @@ expression: json
"items": {
"type": "string"
}
}
},
"markdownDescription": "Custom groups configuration for organizing imports.\nEach array element represents a group, and multiple group names in the same array are treated as one.\nAccepts both `string` and `string[]` as group elements."
},
"ignoreCase": {
"default": true,
Expand Down Expand Up @@ -280,5 +299,6 @@ expression: json
"none"
]
}
}
},
"markdownDescription": "Configuration options for the formatter.\nMost options are the same as Prettier's options.\nSee also <https://prettier.io/docs/options>"
}
Loading
Loading