1use serde_json::{Map, Value};
2
3const BOM_STR: &str = "\u{FEFF}";
5
6#[derive(Debug, Clone)]
8pub struct SortOptions {
9 pub pretty: bool,
11 pub sort_scripts: bool,
13}
14
15impl Default for SortOptions {
16 fn default() -> Self {
17 Self { pretty: true, sort_scripts: false }
18 }
19}
20
21pub fn sort_package_json_with_options(
23 input: &str,
24 options: &SortOptions,
25) -> Result<String, serde_json::Error> {
26 let (has_bom, body) =
27 input.strip_prefix(BOM_STR).map_or((false, input), |stripped| (true, stripped));
28
29 let value: Value = serde_json::from_str(body)?;
30
31 let sorted = match value {
32 Value::Object(obj) => Value::Object(sort_object_keys(obj, options)),
33 other => other,
34 };
35
36 let mut buf: Vec<u8> = Vec::with_capacity(input.len() + 16);
45 if has_bom {
46 buf.extend_from_slice(BOM_STR.as_bytes());
47 }
48 if options.pretty {
49 serde_json::to_writer_pretty(&mut buf, &sorted)?;
50 buf.push(b'\n');
51 } else {
52 serde_json::to_writer(&mut buf, &sorted)?;
53 }
54 Ok(unsafe { String::from_utf8_unchecked(buf) })
58}
59
60pub fn sort_package_json(input: &str) -> Result<String, serde_json::Error> {
62 sort_package_json_with_options(input, &SortOptions::default())
63}
64
65#[inline]
68fn transform_value<F>(value: Value, f: F) -> Value
69where
70 F: FnOnce(Map<String, Value>) -> Map<String, Value>,
71{
72 match value {
73 Value::Object(o) => Value::Object(f(o)),
74 other => other,
75 }
76}
77
78#[inline]
79fn transform_array<F>(value: Value, f: F) -> Value
80where
81 F: FnOnce(Vec<Value>) -> Vec<Value>,
82{
83 match value {
84 Value::Array(arr) => Value::Array(f(arr)),
85 other => other,
86 }
87}
88
89#[inline]
90fn transform_with_key_order(value: Value, key_order: &[&str]) -> Value {
91 transform_value(value, |o| sort_object_by_key_order(o, key_order))
92}
93
94fn sort_object_alphabetically(mut obj: Map<String, Value>) -> Map<String, Value> {
95 obj.sort_keys();
96 obj
97}
98
99fn sort_object_recursive(mut obj: Map<String, Value>) -> Map<String, Value> {
100 sort_object_recursive_in_place(&mut obj);
101 obj
102}
103
104fn sort_object_recursive_in_place(obj: &mut Map<String, Value>) {
105 for value in obj.values_mut() {
106 if let Value::Object(nested) = value {
107 sort_object_recursive_in_place(nested);
108 }
109 }
110 obj.sort_keys();
111}
112
113fn sort_array_unique(mut arr: Vec<Value>) -> Vec<Value> {
115 arr.retain(Value::is_string);
116 arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
118 arr.dedup_by(|a, b| a.as_str() == b.as_str());
119 arr
120}
121
122fn dedupe_array(mut arr: Vec<Value>) -> Vec<Value> {
125 let mut write = 0;
126 for read in 0..arr.len() {
127 let keep = match arr[read].as_str() {
128 Some(s) => !arr[..write].iter().any(|seen| seen.as_str() == Some(s)),
129 None => false,
130 };
131 if keep {
132 if write != read {
133 arr.swap(write, read);
134 }
135 write += 1;
136 }
137 }
138 arr.truncate(write);
139 arr
140}
141
142fn sort_object_by_key_order(obj: Map<String, Value>, key_order: &[&str]) -> Map<String, Value> {
148 let mut known: Vec<Option<(String, Value)>> = (0..key_order.len()).map(|_| None).collect();
149 let mut others: Vec<(String, Value)> = Vec::new();
150
151 for (key, value) in obj {
152 match key_order.iter().position(|kn| *kn == key.as_str()) {
153 Some(idx) => known[idx] = Some((key, value)),
154 None => others.push((key, value)),
155 }
156 }
157
158 others.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
159
160 let mut result = Map::with_capacity(known.len() + others.len());
161 for (key, value) in known.into_iter().flatten() {
162 result.insert(key, value);
163 }
164 for (key, value) in others {
165 result.insert(key, value);
166 }
167 result
168}
169
170fn sort_people_object(obj: Map<String, Value>) -> Map<String, Value> {
171 sort_object_by_key_order(obj, &["name", "email", "url"])
172}
173
174macro_rules! declare_field_order {
181 (
182 $key:ident, $value:ident, $known:ident, $unknown:ident;
183 [ $( $idx:literal => $field_name:literal $( => $transform:expr )? ),* $(,)? ]
184 ) => {
185 match $key.as_str() {
186 $(
187 $field_name => $known.push((
188 $idx,
189 $key,
190 declare_field_order!(@value $value $(, $transform)?),
191 )),
192 )*
193 _ => $unknown.push(($key, $value)),
194 }
195 };
196 (@value $value:ident) => { $value };
197 (@value $value:ident, $transform:expr) => { $transform };
198}
199
200fn sort_object_keys(obj: Map<String, Value>, options: &SortOptions) -> Map<String, Value> {
201 let mut known: Vec<(usize, String, Value)> = Vec::new();
204 let mut unknown: Vec<(String, Value)> = Vec::new();
205
206 for (key, value) in obj {
207 declare_field_order!(key, value, known, unknown; [
208 0 => "$schema",
210 1 => "name",
211 2 => "displayName",
212 3 => "version",
213 4 => "stableVersion",
214 5 => "gitHead",
215 6 => "private",
216 7 => "description",
217 8 => "categories" => transform_array(value, sort_array_unique),
218 9 => "keywords" => transform_array(value, sort_array_unique),
219 10 => "homepage",
220 11 => "bugs" => transform_with_key_order(value, &["url", "email"]),
221 12 => "license",
223 13 => "author" => transform_value(value, sort_people_object),
224 14 => "maintainers",
225 15 => "contributors",
226 16 => "repository" => transform_with_key_order(value, &["type", "url"]),
228 17 => "funding" => transform_with_key_order(value, &["type", "url"]),
229 18 => "donate" => transform_with_key_order(value, &["type", "url"]),
230 19 => "sponsor" => transform_with_key_order(value, &["type", "url"]),
231 20 => "qna",
232 21 => "publisher",
233 22 => "man",
235 23 => "style",
236 24 => "example",
237 25 => "examplestyle",
238 26 => "assets",
239 27 => "bin" => transform_value(value, sort_object_alphabetically),
240 28 => "source",
241 29 => "directories" => transform_with_key_order(value, &["lib", "bin", "man", "doc", "example", "test"]),
242 30 => "workspaces",
243 31 => "binary" => transform_with_key_order(value, &["module_name", "module_path", "remote_path", "package_name", "host"]),
244 32 => "files" => transform_array(value, dedupe_array),
245 33 => "os",
246 34 => "cpu",
247 35 => "libc" => transform_array(value, sort_array_unique),
248 36 => "type",
250 37 => "sideEffects",
251 38 => "main",
252 39 => "module",
253 40 => "browser",
254 41 => "types",
255 42 => "typings",
256 43 => "typesVersions",
257 44 => "typeScriptVersion",
258 45 => "typesPublisherContentHash",
259 46 => "react-native",
260 47 => "svelte",
261 48 => "unpkg",
262 49 => "jsdelivr",
263 50 => "jsnext:main",
264 51 => "umd",
265 52 => "umd:main",
266 53 => "es5",
267 54 => "esm5",
268 55 => "fesm5",
269 56 => "es2015",
270 57 => "esm2015",
271 58 => "fesm2015",
272 59 => "es2020",
273 60 => "esm2020",
274 61 => "fesm2020",
275 62 => "esnext",
276 63 => "imports",
277 64 => "exports",
278 65 => "publishConfig" => transform_value(value, |o| sort_object_keys(o, options)),
279 66 => "scripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
281 67 => "betterScripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
282 68 => "dependencies" => transform_value(value, sort_object_alphabetically),
284 69 => "devDependencies" => transform_value(value, sort_object_alphabetically),
285 70 => "dependenciesMeta",
286 71 => "peerDependencies" => transform_value(value, sort_object_alphabetically),
287 72 => "peerDependenciesMeta",
288 73 => "optionalDependencies" => transform_value(value, sort_object_alphabetically),
289 74 => "bundledDependencies" => transform_array(value, sort_array_unique),
290 75 => "bundleDependencies" => transform_array(value, sort_array_unique),
291 76 => "resolutions" => transform_value(value, sort_object_alphabetically),
292 77 => "overrides" => transform_value(value, sort_object_alphabetically),
293 78 => "husky" => transform_value(value, sort_object_recursive),
295 79 => "simple-git-hooks",
296 80 => "vite-staged",
297 81 => "lint-staged",
298 82 => "nano-staged",
299 83 => "pre-commit",
300 84 => "commitlint" => transform_value(value, sort_object_recursive),
301 85 => "l10n",
303 86 => "contributes",
304 87 => "activationEvents" => transform_array(value, sort_array_unique),
305 88 => "extensionPack" => transform_array(value, sort_array_unique),
306 89 => "extensionDependencies" => transform_array(value, sort_array_unique),
307 90 => "extensionKind" => transform_array(value, sort_array_unique),
308 91 => "icon",
309 92 => "badges",
310 93 => "galleryBanner",
311 94 => "preview",
312 95 => "markdown",
313 96 => "napi" => transform_value(value, sort_object_alphabetically),
315 97 => "flat",
316 98 => "config" => transform_value(value, sort_object_alphabetically),
317 99 => "nodemonConfig" => transform_value(value, sort_object_recursive),
318 100 => "browserify" => transform_value(value, sort_object_recursive),
319 101 => "babel" => transform_value(value, sort_object_recursive),
320 102 => "browserslist",
321 103 => "xo" => transform_value(value, sort_object_recursive),
322 104 => "prettier" => transform_value(value, sort_object_recursive),
323 105 => "eslintConfig" => transform_value(value, sort_object_recursive),
324 106 => "eslintIgnore",
325 107 => "standard" => transform_value(value, sort_object_recursive),
326 108 => "npmpkgjsonlint",
327 109 => "npmPackageJsonLintConfig",
328 110 => "npmpackagejsonlint",
329 111 => "release",
330 112 => "auto-changelog" => transform_value(value, sort_object_recursive),
331 113 => "remarkConfig" => transform_value(value, sort_object_recursive),
332 114 => "stylelint" => transform_value(value, sort_object_recursive),
333 115 => "typescript" => transform_value(value, sort_object_recursive),
334 116 => "typedoc" => transform_value(value, sort_object_recursive),
335 117 => "tshy" => transform_value(value, sort_object_recursive),
336 118 => "tsdown" => transform_value(value, sort_object_recursive),
337 119 => "size-limit",
338 120 => "ava" => transform_value(value, sort_object_recursive),
340 121 => "jest" => transform_value(value, sort_object_recursive),
341 122 => "jest-junit",
342 123 => "jest-stare",
343 124 => "mocha" => transform_value(value, sort_object_recursive),
344 125 => "nyc" => transform_value(value, sort_object_recursive),
345 126 => "c8" => transform_value(value, sort_object_recursive),
346 127 => "tap",
347 128 => "tsd" => transform_value(value, sort_object_recursive),
348 129 => "typeCoverage" => transform_value(value, sort_object_recursive),
349 130 => "oclif" => transform_value(value, sort_object_recursive),
350 131 => "languageName",
352 132 => "preferGlobal",
353 133 => "devEngines" => transform_value(value, sort_object_alphabetically),
354 134 => "engines" => transform_value(value, sort_object_alphabetically),
355 135 => "engineStrict",
356 136 => "volta" => transform_value(value, sort_object_recursive),
357 137 => "packageManager",
358 138 => "pnpm",
359 ]);
360 }
361
362 known.sort_unstable_by_key(|(idx, _, _)| *idx);
363 unknown.sort_unstable_by(|(a, _), (b, _)| {
366 let a_priv = a.starts_with('_');
367 let b_priv = b.starts_with('_');
368 a_priv.cmp(&b_priv).then_with(|| a.cmp(b))
369 });
370
371 let mut result = Map::with_capacity(known.len() + unknown.len());
372 for (_, key, value) in known {
373 result.insert(key, value);
374 }
375 for (key, value) in unknown {
376 result.insert(key, value);
377 }
378 result
379}