From 86c016816aec66834dd64f13920b37af3015ab7b Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:03:00 +0000 Subject: [PATCH] feat(oxfmt/sort_package_json): Handle `oxfmtrc.sort_scripts` option (#17738) Part of #17387 This PR does not affect existing behavior. The next PR will implement the integration and tests. --- Cargo.lock | 1 + apps/oxfmt/src/core/config.rs | 2 +- crates/oxc_formatter/Cargo.toml | 1 + crates/oxc_formatter/src/oxfmtrc.rs | 68 +++++++++++++++++-- .../tests/snapshots/schema_json.snap | 46 +++++++++++-- npm/oxfmt/configuration_schema.json | 45 ++++++++++-- .../src/snapshots/schema_markdown.snap | 12 +++- 7 files changed, 152 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d18dce03139f1..ec45127c662bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1939,6 +1939,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "sort-package-json", "unicode-width", ] diff --git a/apps/oxfmt/src/core/config.rs b/apps/oxfmt/src/core/config.rs index d0dc0b1312244..77c9792d4a515 100644 --- a/apps/oxfmt/src/core/config.rs +++ b/apps/oxfmt/src/core/config.rs @@ -214,7 +214,7 @@ impl ConfigResolver { FormatFileStrategy::ExternalFormatterPackageJson { .. } => { ResolvedOptions::ExternalFormatterPackageJson { external_options, - sort_package_json: oxfmt_options.sort_package_json, + sort_package_json: oxfmt_options.sort_package_json.is_some(), insert_final_newline, } } diff --git a/crates/oxc_formatter/Cargo.toml b/crates/oxc_formatter/Cargo.toml index 7c8759fab8a77..c81221f6ceaff 100644 --- a/crates/oxc_formatter/Cargo.toml +++ b/crates/oxc_formatter/Cargo.toml @@ -35,6 +35,7 @@ rustc-hash = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +sort-package-json = { workspace = true } unicode-width = { workspace = true } [dev-dependencies] diff --git a/crates/oxc_formatter/src/oxfmtrc.rs b/crates/oxc_formatter/src/oxfmtrc.rs index a510cbb53876b..d4635d5eda447 100644 --- a/crates/oxc_formatter/src/oxfmtrc.rs +++ b/crates/oxc_formatter/src/oxfmtrc.rs @@ -89,13 +89,13 @@ pub struct Oxfmtrc { #[serde(skip_serializing_if = "Option::is_none")] pub insert_final_newline: Option, - /// Experimental: Sort import statements. Disabled by default. + /// Experimental: Sort import statements. (Default: disabled) #[serde(skip_serializing_if = "Option::is_none")] pub experimental_sort_imports: Option, /// Experimental: Sort `package.json` keys. (Default: `true`) #[serde(skip_serializing_if = "Option::is_none")] - pub experimental_sort_package_json: Option, + pub experimental_sort_package_json: Option, /// Experimental: Enable Tailwind CSS class sorting in JSX class/className attributes. /// When enabled, class strings will be collected and passed to a callback for sorting. @@ -242,6 +242,55 @@ pub enum SortOrderConfig { Desc, } +/// User-provided configuration for `package.json` sorting. +/// +/// - `true`: Enable sorting with default options +/// - `false`: Disable sorting +/// - `{ sortScripts: true }`: Enable sorting with custom options +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(untagged)] +pub enum SortPackageJsonUserConfig { + Bool(bool), + Object(SortPackageJsonConfig), +} + +impl Default for SortPackageJsonUserConfig { + fn default() -> Self { + Self::Bool(true) + } +} + +impl SortPackageJsonUserConfig { + /// Convert to `sort_package_json::SortOptions`. + /// Returns `None` if sorting is disabled. + pub fn to_sort_options(&self) -> Option { + match self { + Self::Bool(false) => None, + Self::Bool(true) => Some(SortPackageJsonConfig::default().to_sort_options()), + Self::Object(config) => Some(config.to_sort_options()), + } + } +} + +/// Configuration for `package.json` sorting. +#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct SortPackageJsonConfig { + /// Sort the `scripts` field alphabetically. (Default: `false`) + #[serde(skip_serializing_if = "Option::is_none")] + pub sort_scripts: Option, +} + +impl SortPackageJsonConfig { + fn to_sort_options(&self) -> sort_package_json::SortOptions { + sort_package_json::SortOptions { + sort_scripts: self.sort_scripts.unwrap_or(false), + // Small optimization: Prettier will reformat anyway + pretty: false, + } + } +} + /// Configuration for Tailwind CSS class sorting. /// Based on options from `prettier-plugin-tailwindcss`. /// @@ -305,13 +354,17 @@ pub struct TailwindcssConfig { #[derive(Debug, Clone)] pub struct OxfmtOptions { pub ignore_patterns: Vec, - pub sort_package_json: bool, + pub sort_package_json: Option, pub insert_final_newline: bool, } impl Default for OxfmtOptions { fn default() -> Self { - Self { ignore_patterns: vec![], sort_package_json: true, insert_final_newline: true } + Self { + ignore_patterns: vec![], + sort_package_json: Some(SortPackageJsonConfig::default().to_sort_options()), + insert_final_newline: true, + } } } @@ -500,12 +553,15 @@ impl Oxfmtrc { } let mut oxfmt_options = OxfmtOptions::default(); + if let Some(patterns) = self.ignore_patterns { oxfmt_options.ignore_patterns = patterns; } - if let Some(sort_package_json) = self.experimental_sort_package_json { - oxfmt_options.sort_package_json = sort_package_json; + + if let Some(config) = self.experimental_sort_package_json { + oxfmt_options.sort_package_json = config.to_sort_options(); } + if let Some(insert_final_newline) = self.insert_final_newline { oxfmt_options.insert_final_newline = insert_final_newline; } diff --git a/crates/oxc_formatter/tests/snapshots/schema_json.snap b/crates/oxc_formatter/tests/snapshots/schema_json.snap index fea5d7e557184..1b4507eafc019 100644 --- a/crates/oxc_formatter/tests/snapshots/schema_json.snap +++ b/crates/oxc_formatter/tests/snapshots/schema_json.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_formatter/tests/schema.rs +assertion_line: 17 expression: json --- { @@ -134,6 +135,33 @@ expression: json ], "type": "string" }, + "SortPackageJsonConfig": { + "description": "Configuration for `package.json` sorting.", + "markdownDescription": "Configuration for `package.json` sorting.", + "properties": { + "sortScripts": { + "description": "Sort the `scripts` field alphabetically. (Default: `false`)", + "markdownDescription": "Sort the `scripts` field alphabetically. (Default: `false`)", + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "SortPackageJsonUserConfig": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/SortPackageJsonConfig" + } + ], + "description": "User-provided configuration for `package.json` sorting.\n\n- `true`: Enable sorting with default options\n- `false`: Disable sorting\n- `{ sortScripts: true }`: Enable sorting with custom options", + "markdownDescription": "User-provided configuration for `package.json` sorting.\n\n- `true`: Enable sorting with default options\n- `false`: Disable sorting\n- `{ sortScripts: true }`: Enable sorting with custom options" + }, "TailwindcssConfig": { "description": "Configuration for Tailwind CSS class sorting.\nBased on options from `prettier-plugin-tailwindcss`.\n\nNote: All `tailwind` prefixes have been removed from option names.\nFor example, use `config` instead of `tailwindConfig`.\n\nSee ", "markdownDescription": "Configuration for Tailwind CSS class sorting.\nBased on options from `prettier-plugin-tailwindcss`.\n\nNote: All `tailwind` prefixes have been removed from option names.\nFor example, use `config` instead of `tailwindConfig`.\n\nSee ", @@ -268,16 +296,20 @@ expression: json "type": "null" } ], - "description": "Experimental: Sort import statements. Disabled by default.", - "markdownDescription": "Experimental: Sort import statements. Disabled by default." + "description": "Experimental: Sort import statements. (Default: disabled)", + "markdownDescription": "Experimental: Sort import statements. (Default: disabled)" }, "experimentalSortPackageJson": { + "anyOf": [ + { + "$ref": "#/definitions/SortPackageJsonUserConfig" + }, + { + "type": "null" + } + ], "description": "Experimental: Sort `package.json` keys. (Default: `true`)", - "markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)", - "type": [ - "boolean", - "null" - ] + "markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)" }, "experimentalTailwindcss": { "anyOf": [ diff --git a/npm/oxfmt/configuration_schema.json b/npm/oxfmt/configuration_schema.json index 71893b86129f0..6ffaf37912983 100644 --- a/npm/oxfmt/configuration_schema.json +++ b/npm/oxfmt/configuration_schema.json @@ -130,6 +130,33 @@ ], "type": "string" }, + "SortPackageJsonConfig": { + "description": "Configuration for `package.json` sorting.", + "markdownDescription": "Configuration for `package.json` sorting.", + "properties": { + "sortScripts": { + "description": "Sort the `scripts` field alphabetically. (Default: `false`)", + "markdownDescription": "Sort the `scripts` field alphabetically. (Default: `false`)", + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "SortPackageJsonUserConfig": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/SortPackageJsonConfig" + } + ], + "description": "User-provided configuration for `package.json` sorting.\n\n- `true`: Enable sorting with default options\n- `false`: Disable sorting\n- `{ sortScripts: true }`: Enable sorting with custom options", + "markdownDescription": "User-provided configuration for `package.json` sorting.\n\n- `true`: Enable sorting with default options\n- `false`: Disable sorting\n- `{ sortScripts: true }`: Enable sorting with custom options" + }, "TailwindcssConfig": { "description": "Configuration for Tailwind CSS class sorting.\nBased on options from `prettier-plugin-tailwindcss`.\n\nNote: All `tailwind` prefixes have been removed from option names.\nFor example, use `config` instead of `tailwindConfig`.\n\nSee ", "markdownDescription": "Configuration for Tailwind CSS class sorting.\nBased on options from `prettier-plugin-tailwindcss`.\n\nNote: All `tailwind` prefixes have been removed from option names.\nFor example, use `config` instead of `tailwindConfig`.\n\nSee ", @@ -264,16 +291,20 @@ "type": "null" } ], - "description": "Experimental: Sort import statements. Disabled by default.", - "markdownDescription": "Experimental: Sort import statements. Disabled by default." + "description": "Experimental: Sort import statements. (Default: disabled)", + "markdownDescription": "Experimental: Sort import statements. (Default: disabled)" }, "experimentalSortPackageJson": { + "anyOf": [ + { + "$ref": "#/definitions/SortPackageJsonUserConfig" + }, + { + "type": "null" + } + ], "description": "Experimental: Sort `package.json` keys. (Default: `true`)", - "markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)", - "type": [ - "boolean", - "null" - ] + "markdownDescription": "Experimental: Sort `package.json` keys. (Default: `true`)" }, "experimentalTailwindcss": { "anyOf": [ diff --git a/tasks/website_formatter/src/snapshots/schema_markdown.snap b/tasks/website_formatter/src/snapshots/schema_markdown.snap index 09b36da345457..7b7f651eec3a6 100644 --- a/tasks/website_formatter/src/snapshots/schema_markdown.snap +++ b/tasks/website_formatter/src/snapshots/schema_markdown.snap @@ -61,7 +61,7 @@ Which end of line characters to apply. (Default: `"lf"`) type: `object | null` -Experimental: Sort import statements. Disabled by default. +Experimental: Sort import statements. (Default: disabled) ### experimentalSortImports.groups @@ -140,12 +140,20 @@ Sort side-effect imports. (Default: `false`) ## experimentalSortPackageJson -type: `boolean | null` +type: `object | boolean | null` Experimental: Sort `package.json` keys. (Default: `true`) +### experimentalSortPackageJson.sortScripts + +type: `boolean | null` + + +Sort the `scripts` field alphabetically. (Default: `false`) + + ## experimentalTailwindcss type: `object | null`