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
27 changes: 19 additions & 8 deletions apps/oxfmt/src-js/config.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ export type ArrowParensConfig = "always" | "avoid";
export type EmbeddedLanguageFormattingConfig = "auto" | "off";
export type EndOfLineConfig = "lf" | "crlf" | "cr";
export type HtmlWhitespaceSensitivityConfig = "css" | "strict" | "ignore";
export type JsdocUserConfig = boolean | JsdocConfig;
export type ObjectWrapConfig = "preserve" | "collapse";
export type ProseWrapConfig = "always" | "never" | "preserve";
export type QuotePropsConfig = "as-needed" | "consistent" | "preserve";
export type SortImportsUserConfig = boolean | SortImportsConfig;
export type SortGroupItemConfig = NewlinesBetweenMarker | string | string[];
export type SortOrderConfig = "asc" | "desc";
export type SortPackageJsonUserConfig = boolean | SortPackageJsonConfig;
export type SortTailwindcssUserConfig = boolean | SortTailwindcssConfig;
export type TrailingCommaConfig = "all" | "es5" | "none";

/**
Expand Down Expand Up @@ -85,11 +88,11 @@ export interface Oxfmtrc {
* tag aliases are canonicalized, descriptions are capitalized,
* long lines are wrapped, and short comments are collapsed to single-line.
*
* Pass an object (`jsdoc: {}`) to enable with defaults, or omit to disable.
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
jsdoc?: JsdocConfig;
jsdoc?: JsdocUserConfig;
/**
* Use single quotes instead of double quotes in JSX.
*
Expand Down Expand Up @@ -163,9 +166,11 @@ export interface Oxfmtrc {
* Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
* For details, see each field's documentation.
*
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
sortImports?: SortImportsConfig;
sortImports?: SortImportsUserConfig;
/**
* Sort `package.json` keys.
*
Expand All @@ -183,9 +188,11 @@ export interface Oxfmtrc {
* Option names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).
* For details, see each field's documentation.
*
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
sortTailwindcss?: SortTailwindcssConfig;
sortTailwindcss?: SortTailwindcssUserConfig;
/**
* Specify the number of spaces per indentation-level.
*
Expand Down Expand Up @@ -365,11 +372,11 @@ export interface FormatConfig {
* tag aliases are canonicalized, descriptions are capitalized,
* long lines are wrapped, and short comments are collapsed to single-line.
*
* Pass an object (`jsdoc: {}`) to enable with defaults, or omit to disable.
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
jsdoc?: JsdocConfig;
jsdoc?: JsdocUserConfig;
/**
* Use single quotes instead of double quotes in JSX.
*
Expand Down Expand Up @@ -436,9 +443,11 @@ export interface FormatConfig {
* Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
* For details, see each field's documentation.
*
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
sortImports?: SortImportsConfig;
sortImports?: SortImportsUserConfig;
/**
* Sort `package.json` keys.
*
Expand All @@ -456,9 +465,11 @@ export interface FormatConfig {
* Option names omit the `tailwind` prefix used in the original plugin (e.g., `config` instead of `tailwindConfig`).
* For details, see each field's documentation.
*
* Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
*
* - Default: Disabled
*/
sortTailwindcss?: SortTailwindcssConfig;
sortTailwindcss?: SortTailwindcssUserConfig;
/**
* Specify the number of spaces per indentation-level.
*
Expand Down
65 changes: 60 additions & 5 deletions apps/oxfmt/src/core/oxfmtrc/format_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,12 @@ pub struct FormatConfig {
/// Using the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).
/// For details, see each field's documentation.
///
/// Pass `true` or an object to enable with defaults, or omit/set `false` to disable.
///
/// - Default: Disabled
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "experimentalSortImports")]
pub sort_imports: Option<SortImportsConfig>,
pub sort_imports: Option<SortImportsUserConfig>,

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

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

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

Expand Down Expand Up @@ -340,6 +344,23 @@ pub enum HtmlWhitespaceSensitivityConfig {

// ---

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum SortImportsUserConfig {
Bool(bool),
Object(SortImportsConfig),
}

impl SortImportsUserConfig {
pub fn into_config(self) -> Option<SortImportsConfig> {
match self {
Self::Bool(true) => Some(SortImportsConfig::default()),
Self::Bool(false) => None,
Self::Object(config) => Some(config),
}
}
}

#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct SortImportsConfig {
Expand Down Expand Up @@ -579,6 +600,23 @@ impl SortPackageJsonConfig {

// ---

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum SortTailwindcssUserConfig {
Bool(bool),
Object(SortTailwindcssConfig),
}

impl SortTailwindcssUserConfig {
pub fn into_config(self) -> Option<SortTailwindcssConfig> {
match self {
Self::Bool(true) => Some(SortTailwindcssConfig::default()),
Self::Bool(false) => None,
Self::Object(config) => Some(config),
}
}
}

#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct SortTailwindcssConfig {
Expand Down Expand Up @@ -628,6 +666,23 @@ pub struct SortTailwindcssConfig {

// ---

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum JsdocUserConfig {
Bool(bool),
Object(JsdocConfig),
}

impl JsdocUserConfig {
pub fn into_config(self) -> Option<JsdocConfig> {
match self {
Self::Bool(true) => Some(JsdocConfig::default()),
Self::Bool(false) => None,
Self::Object(config) => Some(config),
}
}
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct JsdocConfig {
Expand Down
3 changes: 1 addition & 2 deletions apps/oxfmt/src/core/oxfmtrc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ mod to_external_options;
mod to_oxfmt_options;

pub use format_config::{
EndOfLineConfig, FormatConfig, OxfmtOverrideConfig, Oxfmtrc, SortImportsConfig,
SortPackageJsonUserConfig, SortTailwindcssConfig,
EndOfLineConfig, FormatConfig, OxfmtOverrideConfig, Oxfmtrc, SortPackageJsonUserConfig,
};
pub use to_external_options::{finalize_external_options, sync_external_options};
pub use to_oxfmt_options::{OxfmtOptions, to_oxfmt_options};
36 changes: 31 additions & 5 deletions apps/oxfmt/src/core/oxfmtrc/to_oxfmt_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use oxc_toml::Options as TomlFormatterOptions;

use super::format_config::{
ArrowParensConfig, CustomGroupItemConfig, EmbeddedLanguageFormattingConfig, EndOfLineConfig,
FormatConfig, HtmlWhitespaceSensitivityConfig, ObjectWrapConfig, QuotePropsConfig,
SortGroupItemConfig, SortOrderConfig, SortPackageJsonConfig, TrailingCommaConfig,
FormatConfig, HtmlWhitespaceSensitivityConfig, JsdocUserConfig, ObjectWrapConfig,
QuotePropsConfig, SortGroupItemConfig, SortImportsUserConfig, SortOrderConfig,
SortPackageJsonConfig, SortTailwindcssUserConfig, TrailingCommaConfig,
};

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

// Below are our own extensions

if let Some(sort_imports_config) = config.sort_imports {
if let Some(sort_imports_config) =
config.sort_imports.and_then(SortImportsUserConfig::into_config)
{
let mut sort_imports = SortImportsOptions::default();

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

if let Some(tw_config) = config.sort_tailwindcss {
if let Some(tw_config) =
config.sort_tailwindcss.and_then(SortTailwindcssUserConfig::into_config)
{
format_options.sort_tailwindcss = Some(SortTailwindcssOptions {
config: tw_config.config,
stylesheet: tw_config.stylesheet,
Expand All @@ -284,7 +289,7 @@ pub fn to_oxfmt_options(config: FormatConfig) -> Result<OxfmtOptions, String> {
});
}

if let Some(jsdoc_config) = &config.jsdoc {
if let Some(jsdoc_config) = config.jsdoc.and_then(JsdocUserConfig::into_config) {
let mut opts = oxc_formatter::JsdocOptions::default();
if let Some(v) = jsdoc_config.capitalize_descriptions {
opts.capitalize_descriptions = v;
Expand Down Expand Up @@ -660,4 +665,25 @@ mod tests {
.unwrap();
assert!(to_oxfmt_options(config).is_err_and(|e| e.contains("partitionByNewline")));
}

#[test]
fn test_bool_for_object_options() {
let config: FormatConfig = serde_json::from_str(r#"{"sortImports": true}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.sort_imports.is_some());

let config: FormatConfig = serde_json::from_str(r#"{"sortImports": false}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.sort_imports.is_none());

let config: FormatConfig = serde_json::from_str(r#"{"sortTailwindcss": true}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.sort_tailwindcss.is_some());

let config: FormatConfig = serde_json::from_str(r#"{"sortTailwindcss": false}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.sort_tailwindcss.is_none());

let config: FormatConfig = serde_json::from_str(r#"{"jsdoc": true}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.jsdoc.is_some());

let config: FormatConfig = serde_json::from_str(r#"{"jsdoc": false}"#).unwrap();
assert!(to_oxfmt_options(config).unwrap().format_options.jsdoc.is_none());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ Finished in <variable>ms on 5 files using 1 threads.
--------------------"
`;

exports[`oxfmtrc overrides > object option reset to defaults via \`true\` in override 1`] = `
"--------------------
arguments: --check .
working directory: oxfmtrc_overrides/fixtures/bool_reset_override
exit code: 0
--- STDOUT ---------
Checking formatting...

All matched files use the correct format.
Finished in <variable>ms on 4 files using 1 threads.
--- STDERR ---------

--------------------"
`;

exports[`oxfmtrc overrides > oxfmtrc overrides take precedence over editorconfig 1`] = `
"--------------------
arguments: --check .
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"sortImports": { "order": "desc" },
"overrides": [
{
"files": ["reset-to-default.js"],
"options": { "sortImports": true }
},
{
"files": ["disabled.js"],
"options": { "sortImports": false }
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import z from "z";
import c from "c";
import a from "a";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import z from "z";
import a from "a";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import a from "a";
import c from "c";
import z from "z";
19 changes: 19 additions & 0 deletions apps/oxfmt/test/cli/oxfmtrc_overrides/oxfmtrc_overrides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@ describe("oxfmtrc overrides", () => {
expect(snapshot).toMatchSnapshot();
});

// .oxfmtrc.json:
// sortImports: { "order": "desc" }
// overrides: [
// { files: ["reset-to-default.js"], options: { sortImports: true } },
// { files: ["disabled.js"], options: { sortImports: false } }
// ]
//
// Expected:
// - desc-sorted.js: imports sorted in descending order (base config)
// - reset-to-default.js: imports sorted in ascending order (default, reset via `true`)
// - disabled.js: imports NOT sorted (disabled via `false`)
//
// This test verifies that `true`/`false` can be used to reset/disable an object option in overrides
it("object option reset to defaults via `true` in override", async () => {
const cwd = join(fixturesDir, "bool_reset_override");
const snapshot = await runAndSnapshot(cwd, [["--check", "."]]);
expect(snapshot).toMatchSnapshot();
});

// .oxfmtrc.json:
// experimentalTailwindcss: {}
// overrides: [
Expand Down
Loading
Loading