mod value;
use std::borrow::Cow;
use value::{Object, Value};
const BOM_STR: &str = "\u{FEFF}";
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub struct SortOptions {
pub pretty: bool,
pub sort_scripts: bool,
}
impl SortOptions {
#[must_use]
pub const fn new() -> Self {
Self { pretty: true, sort_scripts: false }
}
#[must_use]
pub const fn with_pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
#[must_use]
pub const fn with_sort_scripts(mut self, sort_scripts: bool) -> Self {
self.sort_scripts = sort_scripts;
self
}
}
impl Default for SortOptions {
fn default() -> Self {
Self::new()
}
}
pub fn sort_package_json_with_options(
input: &str,
options: &SortOptions,
) -> Result<String, serde_json::Error> {
let (has_bom, body) =
input.strip_prefix(BOM_STR).map_or((false, input), |stripped| (true, stripped));
let value: Value<'_> = serde_json::from_str(body)?;
let sorted = match value {
Value::Object(obj) => Value::Object(sort_object_keys(obj, options)),
other => other,
};
let mut buf: Vec<u8> = Vec::with_capacity(input.len() + 16);
if has_bom {
buf.extend_from_slice(BOM_STR.as_bytes());
}
if options.pretty {
serde_json::to_writer_pretty(&mut buf, &sorted)?;
buf.push(b'\n');
} else {
serde_json::to_writer(&mut buf, &sorted)?;
}
Ok(unsafe { String::from_utf8_unchecked(buf) })
}
pub fn sort_package_json(input: &str) -> Result<String, serde_json::Error> {
sort_package_json_with_options(input, &SortOptions::default())
}
#[inline]
fn transform_value<'a, F>(value: Value<'a>, f: F) -> Value<'a>
where
F: FnOnce(Object<'a>) -> Object<'a>,
{
match value {
Value::Object(o) => Value::Object(f(o)),
other => other,
}
}
#[inline]
fn transform_array<'a, F>(value: Value<'a>, f: F) -> Value<'a>
where
F: FnOnce(Vec<Value<'a>>) -> Vec<Value<'a>>,
{
match value {
Value::Array(arr) => Value::Array(f(arr)),
other => other,
}
}
#[inline]
fn transform_with_key_order<'a>(value: Value<'a>, key_order: &[&str]) -> Value<'a> {
transform_value(value, |o| sort_object_by_key_order(o, key_order))
}
fn sort_object_alphabetically(mut obj: Object<'_>) -> Object<'_> {
obj.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
obj
}
fn sort_object_recursive(mut obj: Object<'_>) -> Object<'_> {
sort_object_recursive_in_place(&mut obj);
obj
}
fn sort_object_recursive_in_place(obj: &mut Object<'_>) {
for (_, value) in &mut *obj {
if let Value::Object(nested) = value {
sort_object_recursive_in_place(nested);
}
}
obj.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
}
fn sort_array_unique(mut arr: Vec<Value<'_>>) -> Vec<Value<'_>> {
arr.retain(Value::is_string);
arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
arr.dedup_by(|a, b| a.as_str() == b.as_str());
arr
}
fn dedupe_array(mut arr: Vec<Value<'_>>) -> Vec<Value<'_>> {
let mut write = 0;
for read in 0..arr.len() {
let keep = match arr[read].as_str() {
Some(s) => !arr[..write].iter().any(|seen| seen.as_str() == Some(s)),
None => false,
};
if keep {
if write != read {
arr.swap(write, read);
}
write += 1;
}
}
arr.truncate(write);
arr
}
fn sort_object_by_key_order<'a>(obj: Object<'a>, key_order: &[&str]) -> Object<'a> {
let mut known: Vec<Option<(Cow<'a, str>, Value<'a>)>> =
(0..key_order.len()).map(|_| None).collect();
let mut others: Object<'a> = Vec::new();
for (key, value) in obj {
match key_order.iter().position(|kn| *kn == key.as_ref()) {
Some(idx) => known[idx] = Some((key, value)),
None => others.push((key, value)),
}
}
others.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
let mut result = Vec::with_capacity(known.len() + others.len());
for (key, value) in known.into_iter().flatten() {
result.push((key, value));
}
for (key, value) in others {
result.push((key, value));
}
result
}
fn sort_people_object(obj: Object<'_>) -> Object<'_> {
sort_object_by_key_order(obj, &["name", "email", "url"])
}
fn sort_dev_engine(value: Value<'_>) -> Value<'_> {
const KEY_ORDER: &[&str] = &["name", "version", "onFail"];
match value {
Value::Array(arr) => Value::Array(
arr.into_iter().map(|value| transform_with_key_order(value, KEY_ORDER)).collect(),
),
other => transform_with_key_order(other, KEY_ORDER),
}
}
fn sort_dev_engines(obj: Object<'_>) -> Object<'_> {
let mut obj: Object<'_> =
obj.into_iter().map(|(key, value)| (key, sort_dev_engine(value))).collect();
obj.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
obj
}
macro_rules! declare_field_order {
(
$key:ident, $value:ident, $known:ident, $unknown:ident;
[ $( $idx:literal => $field_name:literal $( => $transform:expr )? ),* $(,)? ]
) => {
match $key.as_ref() {
$(
$field_name => $known.push((
$idx,
$key,
declare_field_order!(@value $value $(, $transform)?),
)),
)*
_ => $unknown.push(($key, $value)),
}
};
(@value $value:ident) => { $value };
(@value $value:ident, $transform:expr) => { $transform };
}
fn sort_object_keys<'a>(obj: Object<'a>, options: &SortOptions) -> Object<'a> {
let mut known: Vec<(usize, Cow<'a, str>, Value<'a>)> = Vec::new();
let mut unknown: Object<'a> = Vec::new();
for (key, value) in obj {
declare_field_order!(key, value, known, unknown; [
0 => "$schema",
1 => "name",
2 => "displayName",
3 => "version",
4 => "stableVersion",
5 => "gitHead",
6 => "private",
7 => "description",
8 => "categories" => transform_array(value, sort_array_unique),
9 => "keywords" => transform_array(value, sort_array_unique),
10 => "homepage",
11 => "bugs" => transform_with_key_order(value, &["url", "email"]),
12 => "license",
13 => "author" => transform_value(value, sort_people_object),
14 => "maintainers",
15 => "contributors",
16 => "repository" => transform_with_key_order(value, &["type", "url"]),
17 => "funding" => transform_with_key_order(value, &["type", "url"]),
18 => "donate" => transform_with_key_order(value, &["type", "url"]),
19 => "sponsor" => transform_with_key_order(value, &["type", "url"]),
20 => "qna",
21 => "publisher",
22 => "man",
23 => "style",
24 => "example",
25 => "examplestyle",
26 => "assets",
27 => "bin" => transform_value(value, sort_object_alphabetically),
28 => "source",
29 => "directories" => transform_with_key_order(value, &["lib", "bin", "man", "doc", "example", "test"]),
30 => "workspaces",
31 => "binary" => transform_with_key_order(value, &["module_name", "module_path", "remote_path", "package_name", "host"]),
32 => "files" => transform_array(value, dedupe_array),
33 => "os",
34 => "cpu",
35 => "libc" => transform_array(value, sort_array_unique),
36 => "type",
37 => "sideEffects",
38 => "main",
39 => "module",
40 => "browser",
41 => "types",
42 => "typings",
43 => "typesVersions",
44 => "typeScriptVersion",
45 => "typesPublisherContentHash",
46 => "react-native",
47 => "svelte",
48 => "unpkg",
49 => "jsdelivr",
50 => "jsnext:main",
51 => "umd",
52 => "umd:main",
53 => "es5",
54 => "esm5",
55 => "fesm5",
56 => "es2015",
57 => "esm2015",
58 => "fesm2015",
59 => "es2020",
60 => "esm2020",
61 => "fesm2020",
62 => "esnext",
63 => "imports",
64 => "exports",
65 => "publishConfig" => transform_value(value, |o| sort_object_keys(o, options)),
66 => "scripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
67 => "betterScripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
68 => "wireit" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
69 => "dependencies" => transform_value(value, sort_object_alphabetically),
70 => "devDependencies" => transform_value(value, sort_object_alphabetically),
71 => "dependenciesMeta",
72 => "peerDependencies" => transform_value(value, sort_object_alphabetically),
73 => "peerDependenciesMeta",
74 => "optionalDependencies" => transform_value(value, sort_object_alphabetically),
75 => "bundledDependencies" => transform_array(value, sort_array_unique),
76 => "bundleDependencies" => transform_array(value, sort_array_unique),
77 => "resolutions" => transform_value(value, sort_object_alphabetically),
78 => "overrides" => transform_value(value, sort_object_alphabetically),
79 => "husky" => transform_value(value, sort_object_recursive),
80 => "simple-git-hooks",
81 => "vite-staged",
82 => "lint-staged",
83 => "nano-staged",
84 => "pre-commit",
85 => "commitlint" => transform_value(value, sort_object_recursive),
86 => "l10n",
87 => "contributes",
88 => "activationEvents" => transform_array(value, sort_array_unique),
89 => "extensionPack" => transform_array(value, sort_array_unique),
90 => "extensionDependencies" => transform_array(value, sort_array_unique),
91 => "extensionKind" => transform_array(value, sort_array_unique),
92 => "icon",
93 => "badges",
94 => "galleryBanner",
95 => "preview",
96 => "markdown",
97 => "napi" => transform_value(value, sort_object_alphabetically),
98 => "flat",
99 => "config" => transform_value(value, sort_object_alphabetically),
100 => "nodemonConfig" => transform_value(value, sort_object_recursive),
101 => "browserify" => transform_value(value, sort_object_recursive),
102 => "babel" => transform_value(value, sort_object_recursive),
103 => "browserslist",
104 => "xo" => transform_value(value, sort_object_recursive),
105 => "prettier" => transform_value(value, sort_object_recursive),
106 => "eslintConfig" => transform_value(value, sort_object_recursive),
107 => "eslintIgnore",
108 => "standard" => transform_value(value, sort_object_recursive),
109 => "npmpkgjsonlint",
110 => "npmPackageJsonLintConfig",
111 => "npmpackagejsonlint",
112 => "release",
113 => "auto-changelog" => transform_value(value, sort_object_alphabetically),
114 => "remarkConfig" => transform_value(value, sort_object_alphabetically),
115 => "stylelint" => transform_value(value, sort_object_recursive),
116 => "typescript" => transform_value(value, sort_object_recursive),
117 => "typedoc" => transform_value(value, sort_object_recursive),
118 => "tshy" => transform_value(value, sort_object_alphabetically),
119 => "tsdown" => transform_value(value, sort_object_recursive),
120 => "size-limit",
121 => "ava" => transform_value(value, sort_object_recursive),
122 => "jest" => transform_value(value, sort_object_alphabetically),
123 => "jest-junit",
124 => "jest-stare",
125 => "mocha" => transform_value(value, sort_object_recursive),
126 => "nyc" => transform_value(value, sort_object_recursive),
127 => "c8" => transform_value(value, sort_object_recursive),
128 => "tap",
129 => "tsd" => transform_value(value, sort_object_recursive),
130 => "typeCoverage" => transform_value(value, sort_object_recursive),
131 => "oclif" => transform_value(value, sort_object_recursive),
132 => "languageName",
133 => "preferGlobal",
134 => "devEngines" => transform_value(value, sort_dev_engines),
135 => "engines" => transform_value(value, sort_object_alphabetically),
136 => "engineStrict",
137 => "volta" => transform_value(value, sort_object_recursive),
138 => "packageManager",
139 => "pnpm",
]);
}
known.sort_unstable_by_key(|(idx, _, _)| *idx);
unknown.sort_unstable_by(|(a, _), (b, _)| {
let a_priv = a.starts_with('_');
let b_priv = b.starts_with('_');
a_priv.cmp(&b_priv).then_with(|| a.cmp(b))
});
let mut result = Vec::with_capacity(known.len() + unknown.len());
for (_, key, value) in known {
result.push((key, value));
}
for (key, value) in unknown {
result.push((key, value));
}
result
}