Skip to content

Commit 50ddc83

Browse files
connorsheaCopilot
authored andcommitted
docs(oxfmt): Improve docs for .oxfmtrc.jsonc config fields and add markdownDescription fields to JSON Schema (#16587)
We should probably also improve the `type: string` stuff in the output Markdown to serialize enums with their actual values, but that's a separate problem. This also updates the logic for generating the oxfmtrc `configuration_schema.json` file so it properly matches the way we generate the configuration_schema.json for oxlint, including with `markdownDescription` fields. See the oxlintrc file [here](https://github.com/oxc-project/oxc/blob/09ca3864227b30fa271d0ff35c5b5aa657f7c575/crates/oxc_linter/src/config/oxlintrc.rs#L205). Before: <img width="633" height="417" alt="Screenshot 2025-12-07 at 10 29 13 PM" src="https://github.com/user-attachments/assets/5cb205e3-2180-445b-a255-10b0831ff38a" /> After: <img width="807" height="213" alt="Screenshot 2025-12-07 at 10 30 07 PM" src="https://github.com/user-attachments/assets/f5b1eb5b-bcb9-4348-8517-12ef3b91b155" />
1 parent 36a3f43 commit 50ddc83

File tree

5 files changed

+204
-117
lines changed

5 files changed

+204
-117
lines changed

crates/oxc_formatter/src/service/oxfmtrc.rs

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::path::Path;
22

3-
use schemars::JsonSchema;
3+
use schemars::{JsonSchema, schema_for};
44
use serde::{Deserialize, Deserializer, Serialize};
55
use serde_json::Value;
66

@@ -17,47 +17,48 @@ use crate::{
1717
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]
1818
#[serde(rename_all = "camelCase", default)]
1919
pub struct Oxfmtrc {
20-
/// Use tabs for indentation or spaces. (Default: false)
20+
/// Use tabs for indentation or spaces. (Default: `false`)
2121
#[serde(skip_serializing_if = "Option::is_none")]
2222
pub use_tabs: Option<bool>,
23-
/// Number of spaces per indentation level. (Default: 2)
23+
/// Number of spaces per indentation level. (Default: `2`)
2424
#[serde(skip_serializing_if = "Option::is_none")]
2525
pub tab_width: Option<u8>,
26-
/// Which end of line characters to apply. (Default: "lf")
26+
/// Which end of line characters to apply. (Default: `"lf"`)
2727
#[serde(skip_serializing_if = "Option::is_none")]
2828
pub end_of_line: Option<EndOfLineConfig>,
29-
/// The line length that the printer will wrap on. (Default: 100)
29+
/// The line length that the printer will wrap on. (Default: `100`)
3030
#[serde(skip_serializing_if = "Option::is_none")]
3131
pub print_width: Option<u16>,
32-
/// Use single quotes instead of double quotes. (Default: false)
32+
/// Use single quotes instead of double quotes. (Default: `false`)
3333
#[serde(skip_serializing_if = "Option::is_none")]
3434
pub single_quote: Option<bool>,
35-
/// Use single quotes instead of double quotes in JSX. (Default: false)
35+
/// Use single quotes instead of double quotes in JSX. (Default: `false`)
3636
#[serde(skip_serializing_if = "Option::is_none")]
3737
pub jsx_single_quote: Option<bool>,
38-
/// Change when properties in objects are quoted. (Default: "as-needed")
38+
/// Change when properties in objects are quoted. (Default: `"as-needed"`)
3939
#[serde(skip_serializing_if = "Option::is_none")]
4040
pub quote_props: Option<QuotePropsConfig>,
41-
/// Print trailing commas wherever possible. (Default: "all")
41+
/// Print trailing commas wherever possible. (Default: `"all"`)
4242
#[serde(skip_serializing_if = "Option::is_none")]
4343
pub trailing_comma: Option<TrailingCommaConfig>,
44-
/// Print semicolons at the ends of statements. (Default: true)
44+
/// Print semicolons at the ends of statements. (Default: `true`)
4545
#[serde(skip_serializing_if = "Option::is_none")]
4646
pub semi: Option<bool>,
47-
/// Include parentheses around a sole arrow function parameter. (Default: "always")
47+
/// Include parentheses around a sole arrow function parameter. (Default: `"always"`)
4848
#[serde(skip_serializing_if = "Option::is_none")]
4949
pub arrow_parens: Option<ArrowParensConfig>,
50-
/// Print spaces between brackets in object literals. (Default: true)
50+
/// Print spaces between brackets in object literals. (Default: `true`)
5151
#[serde(skip_serializing_if = "Option::is_none")]
5252
pub bracket_spacing: Option<bool>,
53-
/// 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)
53+
/// Put the `>` of a multi-line JSX element at the end of the last line
54+
/// instead of being alone on the next line. (Default: `false`)
5455
#[serde(skip_serializing_if = "Option::is_none")]
5556
pub bracket_same_line: Option<bool>,
56-
/// How to wrap object literals when they could fit on one line or span multiple lines. (Default: "preserve")
57-
/// NOTE: In addition to Prettier's "preserve" and "collapse", we also support "always".
57+
/// How to wrap object literals when they could fit on one line or span multiple lines. (Default: `"preserve"`)
58+
/// NOTE: In addition to Prettier's `"preserve"` and `"collapse"`, we also support `"always"`.
5859
#[serde(skip_serializing_if = "Option::is_none")]
5960
pub object_wrap: Option<ObjectWrapConfig>,
60-
/// Put each attribute on a new line in JSX. (Default: false)
61+
/// Put each attribute on a new line in JSX. (Default: `false`)
6162
#[serde(skip_serializing_if = "Option::is_none")]
6263
pub single_attribute_per_line: Option<bool>,
6364

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

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

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

81-
/// Experimental: Sort `package.json` keys. (Default: true)
82+
/// Experimental: Sort `package.json` keys. (Default: `true`)
8283
#[serde(default = "default_true")]
8384
pub experimental_sort_package_json: bool,
8485

@@ -531,6 +532,60 @@ impl Oxfmtrc {
531532
// e.g. `plugins`, `htmlWhitespaceSensitivity`, `vueIndentScriptAndStyle`, etc.
532533
// Other options defined independently by plugins are also left as they are.
533534
}
535+
536+
/// Generates the JSON schema for Oxfmtrc configuration files.
537+
///
538+
/// # Panics
539+
/// Panics if the schema generation fails.
540+
pub fn generate_schema_json() -> String {
541+
let mut schema = schema_for!(Oxfmtrc);
542+
543+
// Allow comments and trailing commas for vscode-json-languageservice
544+
// NOTE: This is NOT part of standard JSON Schema specification
545+
// https://github.com/microsoft/vscode-json-languageservice/blob/fb83547762901f32d8449d57e24666573016b10c/src/jsonLanguageTypes.ts#L151-L159
546+
schema.schema.extensions.insert("allowComments".to_string(), serde_json::Value::Bool(true));
547+
schema
548+
.schema
549+
.extensions
550+
.insert("allowTrailingCommas".to_string(), serde_json::Value::Bool(true));
551+
552+
// Inject markdownDescription fields for better editor support (e.g., VS Code)
553+
let mut json = serde_json::to_value(&schema).unwrap();
554+
Self::inject_markdown_descriptions(&mut json);
555+
556+
serde_json::to_string_pretty(&json).unwrap()
557+
}
558+
559+
/// Recursively inject `markdownDescription` fields into the JSON schema.
560+
/// This is a non-standard field that some editors (like VS Code) use to render
561+
/// markdown in hover tooltips.
562+
fn inject_markdown_descriptions(value: &mut serde_json::Value) {
563+
match value {
564+
serde_json::Value::Object(map) => {
565+
// If this object has a `description` field, copy it to `markdownDescription`
566+
if let Some(serde_json::Value::String(desc_str)) = map.get("description") {
567+
map.insert(
568+
"markdownDescription".to_string(),
569+
serde_json::Value::String(desc_str.clone()),
570+
);
571+
}
572+
573+
// Recursively process all values in the object
574+
for value in map.values_mut() {
575+
Self::inject_markdown_descriptions(value);
576+
}
577+
}
578+
serde_json::Value::Array(items) => {
579+
// Recursively process all items in the array
580+
for item in items {
581+
Self::inject_markdown_descriptions(item);
582+
}
583+
}
584+
_ => {
585+
// Primitive values don't need processing
586+
}
587+
}
588+
}
534589
}
535590

536591
// ---

crates/oxc_formatter/tests/schema.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,7 @@ use project_root::get_project_root;
88
#[test]
99
fn test_schema_json() {
1010
let path = get_project_root().unwrap().join("npm/oxfmt/configuration_schema.json");
11-
let mut schema = schemars::schema_for!(Oxfmtrc);
12-
// Allow comments and trailing commas for vscode-json-languageservice
13-
// NOTE: This is NOT part of standard JSON Schema specification
14-
// https://github.com/microsoft/vscode-json-languageservice/blob/fb83547762901f32d8449d57e24666573016b10c/src/jsonLanguageTypes.ts#L151-L159
15-
schema.schema.extensions.insert("allowComments".to_string(), serde_json::Value::Bool(true));
16-
schema
17-
.schema
18-
.extensions
19-
.insert("allowTrailingCommas".to_string(), serde_json::Value::Bool(true));
20-
let json = serde_json::to_string_pretty(&schema).unwrap();
11+
let json = Oxfmtrc::generate_schema_json();
2112
let existing_json = fs::read_to_string(&path).unwrap_or_default();
2213
if existing_json.trim() != json.trim() {
2314
std::fs::write(&path, &json).unwrap();

crates/oxc_formatter/tests/snapshots/schema_json.snap

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,56 @@ expression: json
99
"type": "object",
1010
"properties": {
1111
"arrowParens": {
12-
"description": "Include parentheses around a sole arrow function parameter. (Default: \"always\")",
12+
"description": "Include parentheses around a sole arrow function parameter. (Default: `\"always\"`)",
1313
"anyOf": [
1414
{
1515
"$ref": "#/definitions/ArrowParensConfig"
1616
},
1717
{
1818
"type": "null"
1919
}
20-
]
20+
],
21+
"markdownDescription": "Include parentheses around a sole arrow function parameter. (Default: `\"always\"`)"
2122
},
2223
"bracketSameLine": {
23-
"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)",
24+
"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`)",
2425
"type": [
2526
"boolean",
2627
"null"
27-
]
28+
],
29+
"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`)"
2830
},
2931
"bracketSpacing": {
30-
"description": "Print spaces between brackets in object literals. (Default: true)",
32+
"description": "Print spaces between brackets in object literals. (Default: `true`)",
3133
"type": [
3234
"boolean",
3335
"null"
34-
]
36+
],
37+
"markdownDescription": "Print spaces between brackets in object literals. (Default: `true`)"
3538
},
3639
"embeddedLanguageFormatting": {
37-
"description": "Control whether formats quoted code embedded in the file. (Default: \"auto\")",
40+
"description": "Control whether formats quoted code embedded in the file. (Default: `\"auto\"`)",
3841
"anyOf": [
3942
{
4043
"$ref": "#/definitions/EmbeddedLanguageFormattingConfig"
4144
},
4245
{
4346
"type": "null"
4447
}
45-
]
48+
],
49+
"markdownDescription": "Control whether formats quoted code embedded in the file. (Default: `\"auto\"`)"
4650
},
4751
"endOfLine": {
48-
"description": "Which end of line characters to apply. (Default: \"lf\")",
52+
"description": "Which end of line characters to apply. (Default: `\"lf\"`)",
4953
"anyOf": [
5054
{
5155
"$ref": "#/definitions/EndOfLineConfig"
5256
},
5357
{
5458
"type": "null"
5559
}
56-
]
60+
],
61+
"markdownDescription": "Which end of line characters to apply. (Default: `\"lf\"`)"
5762
},
5863
"experimentalSortImports": {
5964
"description": "Experimental: Sort import statements. Disabled by default.",
@@ -64,12 +69,14 @@ expression: json
6469
{
6570
"type": "null"
6671
}
67-
]
72+
],
73+
"markdownDescription": "Experimental: Sort import statements. Disabled by default."
6874
},
6975
"experimentalSortPackageJson": {
70-
"description": "Experimental: Sort `package.json` keys. (Default: true)",
76+
"description": "Experimental: Sort `package.json` keys. (Default: `true`)",
7177
"default": true,
72-
"type": "boolean"
78+
"type": "boolean",
79+
"markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)"
7380
},
7481
"ignorePatterns": {
7582
"description": "Ignore files matching these glob patterns. Current working directory is used as the root.",
@@ -79,93 +86,104 @@ expression: json
7986
],
8087
"items": {
8188
"type": "string"
82-
}
89+
},
90+
"markdownDescription": "Ignore files matching these glob patterns. Current working directory is used as the root."
8391
},
8492
"jsxSingleQuote": {
85-
"description": "Use single quotes instead of double quotes in JSX. (Default: false)",
93+
"description": "Use single quotes instead of double quotes in JSX. (Default: `false`)",
8694
"type": [
8795
"boolean",
8896
"null"
89-
]
97+
],
98+
"markdownDescription": "Use single quotes instead of double quotes in JSX. (Default: `false`)"
9099
},
91100
"objectWrap": {
92-
"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\".",
101+
"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\"`.",
93102
"anyOf": [
94103
{
95104
"$ref": "#/definitions/ObjectWrapConfig"
96105
},
97106
{
98107
"type": "null"
99108
}
100-
]
109+
],
110+
"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\"`."
101111
},
102112
"printWidth": {
103-
"description": "The line length that the printer will wrap on. (Default: 100)",
113+
"description": "The line length that the printer will wrap on. (Default: `100`)",
104114
"type": [
105115
"integer",
106116
"null"
107117
],
108118
"format": "uint16",
109-
"minimum": 0.0
119+
"minimum": 0.0,
120+
"markdownDescription": "The line length that the printer will wrap on. (Default: `100`)"
110121
},
111122
"quoteProps": {
112-
"description": "Change when properties in objects are quoted. (Default: \"as-needed\")",
123+
"description": "Change when properties in objects are quoted. (Default: `\"as-needed\"`)",
113124
"anyOf": [
114125
{
115126
"$ref": "#/definitions/QuotePropsConfig"
116127
},
117128
{
118129
"type": "null"
119130
}
120-
]
131+
],
132+
"markdownDescription": "Change when properties in objects are quoted. (Default: `\"as-needed\"`)"
121133
},
122134
"semi": {
123-
"description": "Print semicolons at the ends of statements. (Default: true)",
135+
"description": "Print semicolons at the ends of statements. (Default: `true`)",
124136
"type": [
125137
"boolean",
126138
"null"
127-
]
139+
],
140+
"markdownDescription": "Print semicolons at the ends of statements. (Default: `true`)"
128141
},
129142
"singleAttributePerLine": {
130-
"description": "Put each attribute on a new line in JSX. (Default: false)",
143+
"description": "Put each attribute on a new line in JSX. (Default: `false`)",
131144
"type": [
132145
"boolean",
133146
"null"
134-
]
147+
],
148+
"markdownDescription": "Put each attribute on a new line in JSX. (Default: `false`)"
135149
},
136150
"singleQuote": {
137-
"description": "Use single quotes instead of double quotes. (Default: false)",
151+
"description": "Use single quotes instead of double quotes. (Default: `false`)",
138152
"type": [
139153
"boolean",
140154
"null"
141-
]
155+
],
156+
"markdownDescription": "Use single quotes instead of double quotes. (Default: `false`)"
142157
},
143158
"tabWidth": {
144-
"description": "Number of spaces per indentation level. (Default: 2)",
159+
"description": "Number of spaces per indentation level. (Default: `2`)",
145160
"type": [
146161
"integer",
147162
"null"
148163
],
149164
"format": "uint8",
150-
"minimum": 0.0
165+
"minimum": 0.0,
166+
"markdownDescription": "Number of spaces per indentation level. (Default: `2`)"
151167
},
152168
"trailingComma": {
153-
"description": "Print trailing commas wherever possible. (Default: \"all\")",
169+
"description": "Print trailing commas wherever possible. (Default: `\"all\"`)",
154170
"anyOf": [
155171
{
156172
"$ref": "#/definitions/TrailingCommaConfig"
157173
},
158174
{
159175
"type": "null"
160176
}
161-
]
177+
],
178+
"markdownDescription": "Print trailing commas wherever possible. (Default: `\"all\"`)"
162179
},
163180
"useTabs": {
164-
"description": "Use tabs for indentation or spaces. (Default: false)",
181+
"description": "Use tabs for indentation or spaces. (Default: `false`)",
165182
"type": [
166183
"boolean",
167184
"null"
168-
]
185+
],
186+
"markdownDescription": "Use tabs for indentation or spaces. (Default: `false`)"
169187
}
170188
},
171189
"allowComments": true,
@@ -222,7 +240,8 @@ expression: json
222240
"items": {
223241
"type": "string"
224242
}
225-
}
243+
},
244+
"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."
226245
},
227246
"ignoreCase": {
228247
"default": true,
@@ -280,5 +299,6 @@ expression: json
280299
"none"
281300
]
282301
}
283-
}
302+
},
303+
"markdownDescription": "Configuration options for the formatter.\nMost options are the same as Prettier's options.\nSee also <https://prettier.io/docs/options>"
284304
}

0 commit comments

Comments
 (0)