Bug 1882457 - Update the release process docs for the monorepo migration. r=ahal...
[gecko.git] / devtools / shared / natural-sort.js
blob0b6a30db5f1cb3cbf09cab80955c06c2a52acf3c
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /*
6  * Based on the Natural Sort algorithm for Javascript - Version 0.8.1 - adapted
7  * for Firefox DevTools and released under the MIT license.
8  *
9  * Author: Jim Palmer (based on chunking idea from Dave Koelle)
10  *
11  * Repository:
12  *   https://github.com/overset/javascript-natural-sort/
13  */
15 "use strict";
17 const tokenizeNumbersRx =
18   /(^([+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
19 const hexRx = /^0x[0-9a-f]+$/i;
20 const startsWithNullRx = /^\0/;
21 const endsWithNullRx = /\0$/;
22 const whitespaceRx = /\s+/g;
23 const startsWithZeroRx = /^0/;
24 const versionRx = /^([\w-]+-)?\d+\.\d+\.\d+$/;
25 const numericDateRx = /^\d+[- /]\d+[- /]\d+$/;
27 // If a string contains any of these, we'll try to parse it as a Date
28 const dateKeywords = [
29   "mon",
30   "tues",
31   "wed",
32   "thur",
33   "fri",
34   "sat",
35   "sun",
37   "jan",
38   "feb",
39   "mar",
40   "apr",
41   "may",
42   "jun",
43   "jul",
44   "aug",
45   "sep",
46   "oct",
47   "nov",
48   "dec",
51 /**
52  * Figures whether a given string should be considered by naturalSort to be a
53  * Date, and returns the Date's timestamp if so. Some Date formats, like
54  * single numbers and MM.DD.YYYY, are not supported due to conflicts with things
55  * like version numbers.
56  */
57 function tryParseDate(str) {
58   const lowerCaseStr = str.toLowerCase();
59   return (
60     !versionRx.test(str) &&
61     (numericDateRx.test(str) ||
62       dateKeywords.some(s => lowerCaseStr.includes(s))) &&
63     Date.parse(str)
64   );
67 /**
68  * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
69  * "the way humans do."
70  *
71  * @param  {Object} a
72  *         Passed in by Array.sort(a, b)
73  * @param  {Object} b
74  *         Passed in by Array.sort(a, b)
75  * @param  {String} sessionString
76  *         Client-side value of storage-expires-session l10n string.
77  *         Since this function can be called from both the client and the server,
78  *         and given that client and server might have different locale, we can't compute
79  *         the localized string directly from here.
80  * @param  {Boolean} insensitive
81  *         Should the search be case insensitive?
82  */
83 // eslint-disable-next-line complexity
84 function naturalSort(a = "", b = "", sessionString, insensitive = false) {
85   // Ensure we are working with trimmed strings
86   a = (a + "").trim();
87   b = (b + "").trim();
89   if (insensitive) {
90     a = a.toLowerCase();
91     b = b.toLowerCase();
92     sessionString = sessionString.toLowerCase();
93   }
95   // Chunk/tokenize - Here we split the strings into arrays or strings and
96   // numbers.
97   const aChunks = a
98     .replace(tokenizeNumbersRx, "\0$1\0")
99     .replace(startsWithNullRx, "")
100     .replace(endsWithNullRx, "")
101     .split("\0");
102   const bChunks = b
103     .replace(tokenizeNumbersRx, "\0$1\0")
104     .replace(startsWithNullRx, "")
105     .replace(endsWithNullRx, "")
106     .split("\0");
108   // Hex or date detection.
109   const aHexOrDate = parseInt(a.match(hexRx), 16) || tryParseDate(a);
110   const bHexOrDate = parseInt(b.match(hexRx), 16) || tryParseDate(b);
112   if (
113     (aHexOrDate || bHexOrDate) &&
114     (a === sessionString || b === sessionString)
115   ) {
116     // We have a date and a session string. Move "Session" above the date
117     // (for session cookies)
118     if (a === sessionString) {
119       return -1;
120     } else if (b === sessionString) {
121       return 1;
122     }
123   }
125   // Try and sort Hex codes or Dates.
126   if (aHexOrDate && bHexOrDate) {
127     if (aHexOrDate < bHexOrDate) {
128       return -1;
129     } else if (aHexOrDate > bHexOrDate) {
130       return 1;
131     }
132     return 0;
133   }
135   // Natural sorting through split numeric strings and default strings
136   const aChunksLength = aChunks.length;
137   const bChunksLength = bChunks.length;
138   const maxLen = Math.max(aChunksLength, bChunksLength);
140   for (let i = 0; i < maxLen; i++) {
141     const aChunk = normalizeChunk(aChunks[i] || "", aChunksLength);
142     const bChunk = normalizeChunk(bChunks[i] || "", bChunksLength);
144     // Handle numeric vs string comparison - number < string
145     if (isNaN(aChunk) !== isNaN(bChunk)) {
146       return isNaN(aChunk) ? 1 : -1;
147     }
149     // If unicode use locale comparison
150     // eslint-disable-next-line no-control-regex
151     if (/[^\x00-\x80]/.test(aChunk + bChunk) && aChunk.localeCompare) {
152       const comp = aChunk.localeCompare(bChunk);
153       return comp / Math.abs(comp);
154     }
155     if (aChunk < bChunk) {
156       return -1;
157     } else if (aChunk > bChunk) {
158       return 1;
159     }
160   }
161   return null;
164 // Normalize spaces; find floats not starting with '0', string or 0 if not
165 // defined
166 const normalizeChunk = function (str, length) {
167   return (
168     ((!str.match(startsWithZeroRx) || length == 1) && parseFloat(str)) ||
169     str.replace(whitespaceRx, " ").trim() ||
170     0
171   );
174 exports.naturalSortCaseSensitive = function naturalSortCaseSensitive(
175   a,
176   b,
177   sessionString
178 ) {
179   return naturalSort(a, b, sessionString, false);
182 exports.naturalSortCaseInsensitive = function naturalSortCaseInsensitive(
183   a,
184   b,
185   sessionString
186 ) {
187   return naturalSort(a, b, sessionString, true);