sort_package_json/
lib.rs

1use serde_json::{Map, Value};
2
3/// Options for controlling JSON formatting when sorting
4#[derive(Debug, Clone)]
5pub struct SortOptions {
6    /// Whether to pretty-print the output JSON
7    pub pretty: bool,
8}
9
10impl Default for SortOptions {
11    fn default() -> Self {
12        Self { pretty: true }
13    }
14}
15
16/// Sorts a package.json string with custom options
17pub fn sort_package_json_with_options(
18    input: &str,
19    options: &SortOptions,
20) -> Result<String, serde_json::Error> {
21    let value: Value = serde_json::from_str(input)?;
22
23    let sorted_value =
24        if let Value::Object(obj) = value { Value::Object(sort_object_keys(obj)) } else { value };
25
26    let result = if options.pretty {
27        let mut s = serde_json::to_string_pretty(&sorted_value)?;
28        s.push('\n');
29        s
30    } else {
31        serde_json::to_string(&sorted_value)?
32    };
33
34    Ok(result)
35}
36
37/// Sorts a package.json string with default options (pretty-printed)
38pub fn sort_package_json(input: &str) -> Result<String, serde_json::Error> {
39    sort_package_json_with_options(input, &SortOptions::default())
40}
41
42/// Declares package.json field ordering with transformations.
43///
44/// This macro generates a match statement that handles known package.json fields
45/// in a specific order using explicit indices. It supports optional transformation
46/// expressions for fields that need special processing.
47///
48/// # Usage
49///
50/// ```ignore
51/// declare_field_order!(key, value, known, non_private, private; [
52///     0 => "$schema",
53///     1 => "name",
54///     7 => "categories" => transform_array(&value, sort_array_unique),
55/// ]);
56/// ```
57///
58/// # Parameters
59///
60/// - `key`: The field name identifier
61/// - `value`: The field value identifier
62/// - `known`: The vector to push known fields to
63/// - `non_private`: The vector to push non-private unknown fields to
64/// - `private`: The vector to push private (underscore-prefixed) fields to
65/// - Followed by an array of field declarations in the format:
66///   - `index => "field_name"` for fields without transformation
67///   - `index => "field_name" => transformation_expr` for fields with transformation
68macro_rules! declare_field_order {
69    (
70        $key:ident, $value:ident, $known:ident, $non_private:ident, $private:ident;
71        [
72            $( $idx:literal => $field_name:literal $( => $transform:expr )? ),* $(,)?
73        ]
74    ) => {
75        {
76            // Compile-time validation: ensure indices are literals
77            $( let _ = $idx; )*
78
79            // Generate the match statement
80            match $key.as_str() {
81                $(
82                    $field_name => {
83                        $known.push((
84                            $idx,
85                            $key,
86                            declare_field_order!(@value $value $(, $transform)?)
87                        ));
88                    },
89                )*
90                _ => {
91                    // Unknown field - check if private
92                    if $key.starts_with('_') {
93                        $private.push(($key, $value));
94                    } else {
95                        $non_private.push(($key, $value));
96                    }
97                }
98            }
99        }
100    };
101
102    // Helper: extract value without transformation
103    (@value $value:ident) => { $value };
104
105    // Helper: extract value with transformation
106    (@value $value:ident, $transform:expr) => { $transform };
107}
108
109fn transform_value<F>(value: &Value, transform: F) -> Value
110where
111    F: FnOnce(&Map<String, Value>) -> Map<String, Value>,
112{
113    match value {
114        Value::Object(o) => Value::Object(transform(o)),
115        _ => value.clone(),
116    }
117}
118
119fn transform_array<F>(value: &Value, transform: F) -> Value
120where
121    F: FnOnce(&[Value]) -> Vec<Value>,
122{
123    match value {
124        Value::Array(arr) => Value::Array(transform(arr)),
125        _ => value.clone(),
126    }
127}
128
129fn transform_with_key_order(value: &Value, key_order: &[&str]) -> Value {
130    transform_value(value, |o| sort_object_by_key_order(o, key_order))
131}
132
133fn transform_people_array(value: &Value) -> Value {
134    transform_array(value, |arr| {
135        arr.iter()
136            .map(|v| match v {
137                Value::Object(o) => Value::Object(sort_people_object(o)),
138                _ => v.clone(),
139            })
140            .collect()
141    })
142}
143
144fn sort_object_alphabetically(obj: &Map<String, Value>) -> Map<String, Value> {
145    let mut keys: Vec<&String> = obj.keys().collect();
146    keys.sort();
147
148    let mut result = Map::new();
149    for key in keys {
150        if let Some(value) = obj.get(key) {
151            result.insert(key.clone(), value.clone());
152        }
153    }
154    result
155}
156
157fn sort_object_recursive(obj: &Map<String, Value>) -> Map<String, Value> {
158    let mut keys: Vec<&String> = obj.keys().collect();
159    keys.sort();
160
161    let mut result = Map::new();
162    for key in keys {
163        if let Some(value) = obj.get(key) {
164            let transformed_value = match value {
165                Value::Object(nested) => Value::Object(sort_object_recursive(nested)),
166                _ => value.clone(),
167            };
168            result.insert(key.clone(), transformed_value);
169        }
170    }
171    result
172}
173
174fn sort_array_unique(arr: &[Value]) -> Vec<Value> {
175    let mut strings: Vec<String> =
176        arr.iter().filter_map(|v| v.as_str().map(String::from)).collect();
177
178    strings.sort();
179    strings.dedup();
180
181    strings.into_iter().map(Value::String).collect()
182}
183
184fn sort_object_by_key_order(obj: &Map<String, Value>, key_order: &[&str]) -> Map<String, Value> {
185    let mut result = Map::new();
186
187    // Add keys in specified order
188    for &key in key_order {
189        if let Some(value) = obj.get(key) {
190            result.insert(key.to_string(), value.clone());
191        }
192    }
193
194    // Add remaining keys alphabetically
195    let mut remaining: Vec<&String> =
196        obj.keys().filter(|k| !key_order.contains(&k.as_str())).collect();
197    remaining.sort();
198
199    for key in remaining {
200        if let Some(value) = obj.get(key) {
201            result.insert(key.clone(), value.clone());
202        }
203    }
204
205    result
206}
207
208fn sort_people_object(obj: &Map<String, Value>) -> Map<String, Value> {
209    sort_object_by_key_order(obj, &["name", "email", "url"])
210}
211
212fn sort_exports(obj: &Map<String, Value>) -> Map<String, Value> {
213    let mut paths = Vec::new();
214    let mut types_conds = Vec::new();
215    let mut other_conds = Vec::new();
216    let mut default_cond = None;
217
218    for (key, value) in obj.iter() {
219        if key.starts_with('.') {
220            paths.push(key);
221        } else if key == "default" {
222            default_cond = Some((key, value));
223        } else if key == "types" || key.starts_with("types@") {
224            types_conds.push(key);
225        } else {
226            other_conds.push(key);
227        }
228    }
229
230    // Sort each category
231    paths.sort();
232    types_conds.sort();
233    other_conds.sort();
234
235    let mut result = Map::new();
236
237    // Add in order: paths, types, others, default
238    for key in paths {
239        if let Some(value) = obj.get(key) {
240            let transformed = match value {
241                Value::Object(nested) => Value::Object(sort_exports(nested)),
242                _ => value.clone(),
243            };
244            result.insert(key.clone(), transformed);
245        }
246    }
247
248    for key in types_conds {
249        if let Some(value) = obj.get(key) {
250            result.insert(key.clone(), value.clone());
251        }
252    }
253
254    for key in other_conds {
255        if let Some(value) = obj.get(key) {
256            result.insert(key.clone(), value.clone());
257        }
258    }
259
260    if let Some((key, value)) = default_cond {
261        result.insert(key.clone(), value.clone());
262    }
263
264    result
265}
266
267fn sort_object_keys(obj: Map<String, Value>) -> Map<String, Value> {
268    // Storage for categorized keys with their values and ordering information
269    let mut known: Vec<(usize, String, Value)> = Vec::new(); // (order_index, key, value)
270    let mut non_private: Vec<(String, Value)> = Vec::new();
271    let mut private: Vec<(String, Value)> = Vec::new();
272
273    // Single pass through all keys using into_iter()
274    for (key, value) in obj {
275        declare_field_order!(key, value, known, non_private, private; [
276            0 => "$schema",
277            1 => "name",
278            2 => "displayName",
279            3 => "version",
280            4 => "stableVersion",
281            5 => "private",
282            6 => "description",
283            7 => "categories" => transform_array(&value, sort_array_unique),
284            8 => "keywords" => transform_array(&value, sort_array_unique),
285            9 => "homepage",
286            10 => "bugs" => transform_with_key_order(&value, &["url", "email"]),
287            11 => "repository" => transform_with_key_order(&value, &["type", "url"]),
288            12 => "author" => transform_value(&value, sort_people_object),
289            13 => "maintainers" => transform_people_array(&value),
290            14 => "contributors" => transform_people_array(&value),
291            15 => "donate" => transform_with_key_order(&value, &["type", "url"]),
292            16 => "funding" => transform_with_key_order(&value, &["type", "url"]),
293            17 => "sponsor" => transform_with_key_order(&value, &["type", "url"]),
294            18 => "license",
295            19 => "qna",
296            20 => "publisher",
297            21 => "sideEffects",
298            22 => "type",
299            23 => "main",
300            24 => "imports",
301            25 => "exports" => transform_value(&value, sort_exports),
302            26 => "svelte",
303            27 => "umd:main",
304            28 => "jsdelivr",
305            29 => "unpkg",
306            30 => "module",
307            31 => "esnext",
308            32 => "es2020",
309            33 => "esm2020",
310            34 => "fesm2020",
311            35 => "es2015",
312            36 => "esm2015",
313            37 => "fesm2015",
314            38 => "es5",
315            39 => "esm5",
316            40 => "fesm5",
317            41 => "source",
318            42 => "jsnext:main",
319            43 => "browser",
320            44 => "umd",
321            45 => "react-native",
322            46 => "types",
323            47 => "typesVersions",
324            48 => "typings",
325            49 => "style",
326            50 => "example",
327            51 => "examplestyle",
328            52 => "assets",
329            53 => "bin" => transform_value(&value, sort_object_alphabetically),
330            54 => "man",
331            55 => "directories" => transform_with_key_order(
332                &value,
333                &["lib", "bin", "man", "doc", "example", "test"],
334            ),
335            56 => "files" => transform_array(&value, sort_array_unique),
336            57 => "workspaces",
337            58 => "binary" => transform_with_key_order(
338                &value,
339                &["module_name", "module_path", "remote_path", "package_name", "host"],
340            ),
341            59 => "scripts",
342            60 => "betterScripts",
343            61 => "l10n",
344            62 => "contributes",
345            63 => "activationEvents" => transform_array(&value, sort_array_unique),
346            64 => "husky" => transform_value(&value, sort_object_recursive),
347            65 => "simple-git-hooks",
348            66 => "pre-commit",
349            67 => "commitlint" => transform_value(&value, sort_object_recursive),
350            68 => "lint-staged",
351            69 => "nano-staged",
352            70 => "resolutions" => transform_value(&value, sort_object_alphabetically),
353            71 => "overrides" => transform_value(&value, sort_object_alphabetically),
354            72 => "dependencies" => transform_value(&value, sort_object_alphabetically),
355            73 => "devDependencies" => transform_value(&value, sort_object_alphabetically),
356            74 => "dependenciesMeta",
357            75 => "peerDependencies" => transform_value(&value, sort_object_alphabetically),
358            76 => "peerDependenciesMeta",
359            77 => "optionalDependencies" => transform_value(&value, sort_object_alphabetically),
360            78 => "bundledDependencies" => transform_array(&value, sort_array_unique),
361            79 => "bundleDependencies" => transform_array(&value, sort_array_unique),
362            80 => "napi" => transform_value(&value, sort_object_alphabetically),
363            81 => "extensionPack" => transform_array(&value, sort_array_unique),
364            82 => "extensionDependencies" => transform_array(&value, sort_array_unique),
365            83 => "extensionKind" => transform_array(&value, sort_array_unique),
366            84 => "flat",
367            85 => "packageManager",
368            86 => "config" => transform_value(&value, sort_object_alphabetically),
369            87 => "nodemonConfig" => transform_value(&value, sort_object_recursive),
370            88 => "browserify" => transform_value(&value, sort_object_recursive),
371            89 => "babel" => transform_value(&value, sort_object_recursive),
372            90 => "browserslist",
373            91 => "xo" => transform_value(&value, sort_object_recursive),
374            92 => "prettier" => transform_value(&value, sort_object_recursive),
375            93 => "eslintConfig" => transform_value(&value, sort_object_recursive),
376            94 => "eslintIgnore",
377            95 => "npmpkgjsonlint",
378            96 => "npmPackageJsonLintConfig",
379            97 => "npmpackagejsonlint",
380            98 => "release",
381            99 => "remarkConfig" => transform_value(&value, sort_object_recursive),
382            100 => "stylelint" => transform_value(&value, sort_object_recursive),
383            101 => "ava" => transform_value(&value, sort_object_recursive),
384            102 => "jest" => transform_value(&value, sort_object_recursive),
385            103 => "jest-junit",
386            104 => "jest-stare",
387            105 => "mocha" => transform_value(&value, sort_object_recursive),
388            106 => "nyc" => transform_value(&value, sort_object_recursive),
389            107 => "c8" => transform_value(&value, sort_object_recursive),
390            108 => "tap",
391            109 => "oclif" => transform_value(&value, sort_object_recursive),
392            110 => "engines" => transform_value(&value, sort_object_alphabetically),
393            111 => "engineStrict",
394            112 => "volta" => transform_value(&value, sort_object_recursive),
395            113 => "languageName",
396            114 => "os",
397            115 => "cpu",
398            116 => "libc" => transform_array(&value, sort_array_unique),
399            117 => "devEngines" => transform_value(&value, sort_object_alphabetically),
400            118 => "preferGlobal",
401            119 => "publishConfig" => transform_value(&value, sort_object_alphabetically),
402            120 => "icon",
403            121 => "badges",
404            122 => "galleryBanner",
405            123 => "preview",
406            124 => "markdown",
407            125 => "pnpm",
408        ]);
409    }
410
411    // Sort each category (using unstable sort for better performance)
412    known.sort_unstable_by_key(|(index, _, _)| *index);
413    non_private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
414    private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
415
416    // Build result map
417    let mut result = Map::new();
418
419    // Insert known fields (already transformed)
420    for (_index, key, value) in known {
421        result.insert(key, value);
422    }
423
424    // Insert non-private unknown fields
425    for (key, value) in non_private {
426        result.insert(key, value);
427    }
428
429    // Insert private fields
430    for (key, value) in private {
431        result.insert(key, value);
432    }
433
434    result
435}