Skip to content

Commit 6ef440a

Browse files
committed
feat(oxfmt): Support bool for object style options (#20853)
Fixes #20793
1 parent 519e0b8 commit 6ef440a

13 files changed

Lines changed: 278 additions & 64 deletions

File tree

apps/oxfmt/src-js/config.generated.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ export type ArrowParensConfig = "always" | "avoid";
77
export type EmbeddedLanguageFormattingConfig = "auto" | "off";
88
export type EndOfLineConfig = "lf" | "crlf" | "cr";
99
export type HtmlWhitespaceSensitivityConfig = "css" | "strict" | "ignore";
10+
export type JsdocUserConfig = boolean | JsdocConfig;
1011
export type ObjectWrapConfig = "preserve" | "collapse";
1112
export type ProseWrapConfig = "always" | "never" | "preserve";
1213
export type QuotePropsConfig = "as-needed" | "consistent" | "preserve";
14+
export type SortImportsUserConfig = boolean | SortImportsConfig;
1315
export type SortGroupItemConfig = NewlinesBetweenMarker | string | string[];
1416
export type SortOrderConfig = "asc" | "desc";
1517
export type SortPackageJsonUserConfig = boolean | SortPackageJsonConfig;
18+
export type SortTailwindcssUserConfig = boolean | SortTailwindcssConfig;
1619
export type TrailingCommaConfig = "all" | "es5" | "none";
1720

1821
/**
@@ -85,11 +88,11 @@ export interface Oxfmtrc {
8588
* tag aliases are canonicalized, descriptions are capitalized,
8689
* long lines are wrapped, and short comments are collapsed to single-line.
8790
*
88-
* Pass an object (`jsdoc: {}`) to enable with defaults, or omit to disable.
91+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
8992
*
9093
* - Default: Disabled
9194
*/
92-
jsdoc?: JsdocConfig;
95+
jsdoc?: JsdocUserConfig;
9396
/**
9497
* Use single quotes instead of double quotes in JSX.
9598
*
@@ -163,9 +166,11 @@ export interface Oxfmtrc {
163166
* Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
164167
* For details, see each field's documentation.
165168
*
169+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
170+
*
166171
* - Default: Disabled
167172
*/
168-
sortImports?: SortImportsConfig;
173+
sortImports?: SortImportsUserConfig;
169174
/**
170175
* Sort `package.json` keys.
171176
*
@@ -183,9 +188,11 @@ export interface Oxfmtrc {
183188
* Option names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).
184189
* For details, see each field's documentation.
185190
*
191+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
192+
*
186193
* - Default: Disabled
187194
*/
188-
sortTailwindcss?: SortTailwindcssConfig;
195+
sortTailwindcss?: SortTailwindcssUserConfig;
189196
/**
190197
* Specify the number of spaces per indentation-level.
191198
*
@@ -365,11 +372,11 @@ export interface FormatConfig {
365372
* tag aliases are canonicalized, descriptions are capitalized,
366373
* long lines are wrapped, and short comments are collapsed to single-line.
367374
*
368-
* Pass an object (`jsdoc: {}`) to enable with defaults, or omit to disable.
375+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
369376
*
370377
* - Default: Disabled
371378
*/
372-
jsdoc?: JsdocConfig;
379+
jsdoc?: JsdocUserConfig;
373380
/**
374381
* Use single quotes instead of double quotes in JSX.
375382
*
@@ -436,9 +443,11 @@ export interface FormatConfig {
436443
* Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
437444
* For details, see each field's documentation.
438445
*
446+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
447+
*
439448
* - Default: Disabled
440449
*/
441-
sortImports?: SortImportsConfig;
450+
sortImports?: SortImportsUserConfig;
442451
/**
443452
* Sort `package.json` keys.
444453
*
@@ -456,9 +465,11 @@ export interface FormatConfig {
456465
* Option names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).
457466
* For details, see each field's documentation.
458467
*
468+
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
469+
*
459470
* - Default: Disabled
460471
*/
461-
sortTailwindcss?: SortTailwindcssConfig;
472+
sortTailwindcss?: SortTailwindcssUserConfig;
462473
/**
463474
* Specify the number of spaces per indentation-level.
464475
*

apps/oxfmt/src/core/oxfmtrc/format_config.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,12 @@ pub struct FormatConfig {
197197
/// Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
198198
/// For details, see each field's documentation.
199199
///
200+
/// Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
201+
///
200202
/// - Default: Disabled
201203
#[serde(skip_serializing_if = "Option::is_none")]
202204
#[serde(alias = "experimentalSortImports")]
203-
pub sort_imports: Option<SortImportsConfig>,
205+
pub sort_imports: Option<SortImportsUserConfig>,
204206

205207
/// Sort `package.json` keys.
206208
///
@@ -219,30 +221,32 @@ pub struct FormatConfig {
219221
/// Option names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).
220222
/// For details, see each field's documentation.
221223
///
224+
/// Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
225+
///
222226
/// - Default: Disabled
223227
#[serde(skip_serializing_if = "Option::is_none")]
224228
#[serde(alias = "experimentalTailwindcss")]
225-
pub sort_tailwindcss: Option<SortTailwindcssConfig>,
229+
pub sort_tailwindcss: Option<SortTailwindcssUserConfig>,
226230

227231
/// Enable JSDoc comment formatting.
228232
///
229233
/// When enabled, JSDoc comments are normalized and reformatted:
230234
/// tag aliases are canonicalized, descriptions are capitalized,
231235
/// long lines are wrapped, and short comments are collapsed to single-line.
232236
///
233-
/// Pass an object (`jsdoc: {}`) to enable with defaults, or omit to disable.
237+
/// Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
234238
///
235239
/// - Default: Disabled
236240
#[serde(skip_serializing_if = "Option::is_none", default)]
237-
pub jsdoc: Option<JsdocConfig>,
241+
pub jsdoc: Option<JsdocUserConfig>,
238242
}
239243

240244
impl FormatConfig {
241245
/// Resolve relative tailwind paths (`config`, `stylesheet`) to absolute paths.
242246
/// Otherwise, the plugin tries to resolve the Prettier's configuration file, not Oxfmt's.
243247
/// <https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/125a8bc77639529a5a0c7e4e8a02174d7ed2d70b/src/config.ts#L50-L54>
244248
pub fn resolve_tailwind_paths(&mut self, base_dir: &Path) {
245-
let Some(ref mut tw) = self.sort_tailwindcss else {
249+
let Some(SortTailwindcssUserConfig::Object(ref mut tw)) = self.sort_tailwindcss else {
246250
return;
247251
};
248252

@@ -340,6 +344,23 @@ pub enum HtmlWhitespaceSensitivityConfig {
340344

341345
// ---
342346

347+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
348+
#[serde(untagged)]
349+
pub enum SortImportsUserConfig {
350+
Bool(bool),
351+
Object(SortImportsConfig),
352+
}
353+
354+
impl SortImportsUserConfig {
355+
pub fn into_config(self) -> Option<SortImportsConfig> {
356+
match self {
357+
Self::Bool(true) => Some(SortImportsConfig::default()),
358+
Self::Bool(false) => None,
359+
Self::Object(config) => Some(config),
360+
}
361+
}
362+
}
363+
343364
#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)]
344365
#[serde(rename_all = "camelCase", default)]
345366
pub struct SortImportsConfig {
@@ -579,6 +600,23 @@ impl SortPackageJsonConfig {
579600

580601
// ---
581602

603+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
604+
#[serde(untagged)]
605+
pub enum SortTailwindcssUserConfig {
606+
Bool(bool),
607+
Object(SortTailwindcssConfig),
608+
}
609+
610+
impl SortTailwindcssUserConfig {
611+
pub fn into_config(self) -> Option<SortTailwindcssConfig> {
612+
match self {
613+
Self::Bool(true) => Some(SortTailwindcssConfig::default()),
614+
Self::Bool(false) => None,
615+
Self::Object(config) => Some(config),
616+
}
617+
}
618+
}
619+
582620
#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)]
583621
#[serde(rename_all = "camelCase", default)]
584622
pub struct SortTailwindcssConfig {
@@ -628,6 +666,23 @@ pub struct SortTailwindcssConfig {
628666

629667
// ---
630668

669+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
670+
#[serde(untagged)]
671+
pub enum JsdocUserConfig {
672+
Bool(bool),
673+
Object(JsdocConfig),
674+
}
675+
676+
impl JsdocUserConfig {
677+
pub fn into_config(self) -> Option<JsdocConfig> {
678+
match self {
679+
Self::Bool(true) => Some(JsdocConfig::default()),
680+
Self::Bool(false) => None,
681+
Self::Object(config) => Some(config),
682+
}
683+
}
684+
}
685+
631686
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
632687
#[serde(rename_all = "camelCase")]
633688
pub struct JsdocConfig {

apps/oxfmt/src/core/oxfmtrc/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ mod to_external_options;
33
mod to_oxfmt_options;
44

55
pub use format_config::{
6-
EndOfLineConfig, FormatConfig, OxfmtOverrideConfig, Oxfmtrc, SortImportsConfig,
7-
SortPackageJsonUserConfig, SortTailwindcssConfig,
6+
EndOfLineConfig, FormatConfig, OxfmtOverrideConfig, Oxfmtrc, SortPackageJsonUserConfig,
87
};
98
pub use to_external_options::{finalize_external_options, sync_external_options};
109
pub use to_oxfmt_options::{OxfmtOptions, to_oxfmt_options};

apps/oxfmt/src/core/oxfmtrc/to_oxfmt_options.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use oxc_toml::Options as TomlFormatterOptions;
1010

1111
use super::format_config::{
1212
ArrowParensConfig, CustomGroupItemConfig, EmbeddedLanguageFormattingConfig, EndOfLineConfig,
13-
FormatConfig, HtmlWhitespaceSensitivityConfig, ObjectWrapConfig, QuotePropsConfig,
14-
SortGroupItemConfig, SortOrderConfig, SortPackageJsonConfig, TrailingCommaConfig,
13+
FormatConfig, HtmlWhitespaceSensitivityConfig, JsdocUserConfig, ObjectWrapConfig,
14+
QuotePropsConfig, SortGroupItemConfig, SortImportsUserConfig, SortOrderConfig,
15+
SortPackageJsonConfig, SortTailwindcssUserConfig, TrailingCommaConfig,
1516
};
1617

1718
/// Resolved format options from `FormatConfig`.
@@ -157,7 +158,9 @@ pub fn to_oxfmt_options(config: FormatConfig) -> Result<OxfmtOptions, String> {
157158

158159
// Below are our own extensions
159160

160-
if let Some(sort_imports_config) = config.sort_imports {
161+
if let Some(sort_imports_config) =
162+
config.sort_imports.and_then(SortImportsUserConfig::into_config)
163+
{
161164
let mut sort_imports = SortImportsOptions::default();
162165

163166
if let Some(v) = sort_imports_config.partition_by_newline {
@@ -273,7 +276,9 @@ pub fn to_oxfmt_options(config: FormatConfig) -> Result<OxfmtOptions, String> {
273276
format_options.sort_imports = Some(sort_imports);
274277
}
275278

276-
if let Some(tw_config) = config.sort_tailwindcss {
279+
if let Some(tw_config) =
280+
config.sort_tailwindcss.and_then(SortTailwindcssUserConfig::into_config)
281+
{
277282
format_options.sort_tailwindcss = Some(SortTailwindcssOptions {
278283
config: tw_config.config,
279284
stylesheet: tw_config.stylesheet,
@@ -284,7 +289,7 @@ pub fn to_oxfmt_options(config: FormatConfig) -> Result<OxfmtOptions, String> {
284289
});
285290
}
286291

287-
if let Some(jsdoc_config) = &config.jsdoc {
292+
if let Some(jsdoc_config) = config.jsdoc.and_then(JsdocUserConfig::into_config) {
288293
let mut opts = oxc_formatter::JsdocOptions::default();
289294
if let Some(v) = jsdoc_config.capitalize_descriptions {
290295
opts.capitalize_descriptions = v;
@@ -660,4 +665,25 @@ mod tests {
660665
.unwrap();
661666
assert!(to_oxfmt_options(config).is_err_and(|e| e.contains("partitionByNewline")));
662667
}
668+
669+
#[test]
670+
fn test_bool_for_object_options() {
671+
let config: FormatConfig = serde_json::from_str(r#"{"sortImports": true}"#).unwrap();
672+
assert!(to_oxfmt_options(config).unwrap().format_options.sort_imports.is_some());
673+
674+
let config: FormatConfig = serde_json::from_str(r#"{"sortImports": false}"#).unwrap();
675+
assert!(to_oxfmt_options(config).unwrap().format_options.sort_imports.is_none());
676+
677+
let config: FormatConfig = serde_json::from_str(r#"{"sortTailwindcss": true}"#).unwrap();
678+
assert!(to_oxfmt_options(config).unwrap().format_options.sort_tailwindcss.is_some());
679+
680+
let config: FormatConfig = serde_json::from_str(r#"{"sortTailwindcss": false}"#).unwrap();
681+
assert!(to_oxfmt_options(config).unwrap().format_options.sort_tailwindcss.is_none());
682+
683+
let config: FormatConfig = serde_json::from_str(r#"{"jsdoc": true}"#).unwrap();
684+
assert!(to_oxfmt_options(config).unwrap().format_options.jsdoc.is_some());
685+
686+
let config: FormatConfig = serde_json::from_str(r#"{"jsdoc": false}"#).unwrap();
687+
assert!(to_oxfmt_options(config).unwrap().format_options.jsdoc.is_none());
688+
}
663689
}

apps/oxfmt/test/cli/oxfmtrc_overrides/__snapshots__/oxfmtrc_overrides.test.ts.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ Finished in <variable>ms on 5 files using 1 threads.
7575
--------------------"
7676
`;
7777
78+
exports[`oxfmtrc overrides > object option reset to defaults via \`true\` in override 1`] = `
79+
"--------------------
80+
arguments: --check .
81+
working directory: oxfmtrc_overrides/fixtures/bool_reset_override
82+
exit code: 0
83+
--- STDOUT ---------
84+
Checking formatting...
85+
86+
All matched files use the correct format.
87+
Finished in <variable>ms on 4 files using 1 threads.
88+
--- STDERR ---------
89+
90+
--------------------"
91+
`;
92+
7893
exports[`oxfmtrc overrides > oxfmtrc overrides take precedence over editorconfig 1`] = `
7994
"--------------------
8095
arguments: --check .
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"sortImports": { "order": "desc" },
3+
"overrides": [
4+
{
5+
"files": ["reset-to-default.js"],
6+
"options": { "sortImports": true }
7+
},
8+
{
9+
"files": ["disabled.js"],
10+
"options": { "sortImports": false }
11+
}
12+
]
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import z from "z";
2+
import c from "c";
3+
import a from "a";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import z from "z";
2+
import a from "a";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import a from "a";
2+
import c from "c";
3+
import z from "z";

apps/oxfmt/test/cli/oxfmtrc_overrides/oxfmtrc_overrides.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ describe("oxfmtrc overrides", () => {
106106
expect(snapshot).toMatchSnapshot();
107107
});
108108

109+
// .oxfmtrc.json:
110+
// sortImports: { "order": "desc" }
111+
// overrides: [
112+
// { files: ["reset-to-default.js"], options: { sortImports: true } },
113+
// { files: ["disabled.js"], options: { sortImports: false } }
114+
// ]
115+
//
116+
// Expected:
117+
// - desc-sorted.js: imports sorted in descending order (base config)
118+
// - reset-to-default.js: imports sorted in ascending order (default, reset via `true`)
119+
// - disabled.js: imports NOT sorted (disabled via `false`)
120+
//
121+
// This test verifies that `true`/`false` can be used to reset/disable an object option in overrides
122+
it("object option reset to defaults via `true` in override", async () => {
123+
const cwd = join(fixturesDir, "bool_reset_override");
124+
const snapshot = await runAndSnapshot(cwd, [["--check", "."]]);
125+
expect(snapshot).toMatchSnapshot();
126+
});
127+
109128
// .oxfmtrc.json:
110129
// experimentalTailwindcss: {}
111130
// overrides: [

0 commit comments

Comments
 (0)