Skip to content
Closed
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
21 changes: 16 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/oxfmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ oxc_formatter = { workspace = true }
oxc_language_server = { workspace = true, default-features = false, features = ["formatter"] }
oxc_parser = { workspace = true }
oxc_span = { workspace = true }
sort-package-json = "0.0.1"

bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] }
cow-utils = { workspace = true }
Expand Down
23 changes: 22 additions & 1 deletion apps/oxfmt/src/core/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use oxc_parser::Parser;
use oxc_span::SourceType;

use super::FormatFileSource;
#[cfg(feature = "napi")]
use super::package_json_sorter::sort_package_json_content;

pub enum FormatResult {
Success { is_changed: bool, code: String },
Expand Down Expand Up @@ -114,6 +116,7 @@ impl SourceFormatter {
}

/// Format non-JS/TS file using external formatter (Prettier).
/// For package.json files, sorts them first before formatting.
#[cfg(feature = "napi")]
fn format_by_external_formatter(
&self,
Expand All @@ -126,7 +129,25 @@ impl SourceFormatter {
.as_ref()
.expect("`external_formatter` must exist when `napi` feature is enabled");

match external_formatter.format_file(parser_name, source_text) {
// Special handling for package.json: sort before formatting
let code_to_format = if parser_name == "json-stringify"
&& path.file_name().and_then(|f| f.to_str()) == Some("package.json")
{
// Sort package.json content first
match sort_package_json_content(source_text) {
Ok(sorted) => sorted,
Err(err) => {
// If sorting fails, return error - don't fall back to unsorted
return FormatResult::Error(vec![err]);
}
}
} else {
// For all other files, use original source
source_text.to_string()
};

// Format with Prettier
match external_formatter.format_file(parser_name, &code_to_format) {
Ok(code) => FormatResult::Success { is_changed: source_text != code, code },
Err(err) => FormatResult::Error(vec![OxcDiagnostic::error(format!(
"Failed to format file with external formatter: {}\n{err}",
Expand Down
2 changes: 2 additions & 0 deletions apps/oxfmt/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ mod support;

#[cfg(feature = "napi")]
mod external_formatter;
#[cfg(feature = "napi")]
mod package_json_sorter;

pub use format::{FormatResult, SourceFormatter};
pub use support::FormatFileSource;
Expand Down
58 changes: 58 additions & 0 deletions apps/oxfmt/src/core/package_json_sorter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Package.json sorting functionality using the sort-package-json crate.

use oxc_diagnostics::OxcDiagnostic;

/// Sort a package.json file's content.
///
/// Uses the sort-package-json crate to reorder top-level fields
/// according to well-established npm conventions.
///
/// # Arguments
/// * `source_text` - The raw package.json content as a string
///
/// # Returns
/// * `Ok(String)` - The sorted JSON content
/// * `Err(OxcDiagnostic)` - If parsing or sorting fails
pub fn sort_package_json_content(source_text: &str) -> Result<String, OxcDiagnostic> {
sort_package_json::sort_package_json(source_text).map_err(|err| {
OxcDiagnostic::error(format!("Failed to parse package.json for sorting: {err}"))
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_sort_package_json_basic() {
let input = r#"{"version":"1.0.0","name":"test"}"#;
let result = sort_package_json_content(input).unwrap();
// Name should come before version
assert!(result.find("\"name\"").unwrap() < result.find("\"version\"").unwrap());
}

#[test]
fn test_sort_package_json_preserves_data() {
let input = r#"{"scripts":{"test":"echo"},"name":"test","version":"1.0.0"}"#;
let result = sort_package_json_content(input).unwrap();
assert!(result.contains("\"name\""));
assert!(result.contains("\"version\""));
assert!(result.contains("\"scripts\""));
assert!(result.contains("\"test\""));
assert!(result.contains("\"echo\""));
}

#[test]
fn test_sort_package_json_invalid_json() {
let input = r#"{"name": invalid}"#;
let result = sort_package_json_content(input);
assert!(result.is_err());
}

#[test]
fn test_sort_package_json_empty_object() {
let input = r#"{}"#;
let result = sort_package_json_content(input).unwrap();
assert_eq!(result.trim(), "{}");
}
}
Loading