fix(sonar): spell out the order these five sorts already have

SonarCloud's S2871 fires on a bare .sort() over strings and asks for localeCompare, and it
had five of them on new code, which is what put new reliability at D and failed the gate.

All five sort ISO dates or query parameter names, where the default lexicographic order is
exactly the one wanted. Taking the rule's suggestion literally would have been the wrong
fix twice over: a locale-aware comparison reorders dates differently per runtime, and on
the Amap signature it would produce a canonical string Amap itself does not compute, so
every signed request would come back rejected.

So the comparator is written out rather than changed. Byte order, explicitly, with a line
saying why it has to stay byte order. Behaviour is identical: the 43 Amap cases, the 40
track cases and the 30 client cases over these modules all pass unchanged.
This commit is contained in:
Maurice
2026-09-17 16:19:02 +02:00
parent ce5cc49b58
commit 08df1812be
5 changed files with 15 additions and 5 deletions
@@ -34,7 +34,9 @@ export function groupByDay(suggestions: DawarichSuggestion[]): SuggestionDay[] {
else byDate.set(stay.localDate, [stay])
}
const oldestFirst = [...byDate.keys()].sort()
// ISO dates, so lexicographic order IS chronological order. Spelled out rather than
// left bare, and deliberately not locale-aware, which would vary by runtime.
const oldestFirst = [...byDate.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
return oldestFirst
.map((date, index) => ({
date,
+3 -1
View File
@@ -130,7 +130,9 @@ export function collapsedDayDates(
const foldedInPlan = !!expandedDayIds && !expandedDayIds.has(day.id)
if (foldedInPlan || collapsedDayIds?.has(day.id)) dates.add(day.date)
}
return [...dates].sort()
// Explicit rather than bare: these are ISO dates, where lexicographic IS chronological,
// and a locale-aware comparison would reorder them differently per runtime.
return [...dates].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
}
/** The overlay as a GeoJSON FeatureCollection, for the GL renderer. */
+3 -1
View File
@@ -37,7 +37,9 @@ export function useDawarichJournalTrail(
// here rather than trusting insertion order.
const window = useMemo(() => {
if (dates.size === 0) return null
const sorted = [...dates].sort()
// ISO dates: lexicographic order is chronological, and staying locale-independent
// keeps the window identical on every runtime.
const sorted = [...dates].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
return { from: `${sorted[0]}T00:00:00Z`, to: `${sorted[sorted.length - 1]}T23:59:59Z` }
}, [dates])
@@ -340,7 +340,8 @@ export function bucketPointsByDay(
else byDay.set(date, [{ lat, lng, ts }]);
}
const dates = [...byDay.keys()].sort();
// ISO dates, so lexicographic order is chronological. Explicit and locale-independent.
const dates = [...byDay.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
const days: DawarichTrackDay[] = [];
for (let d = 0; d < dates.length; d++) {
@@ -203,7 +203,10 @@ export class AmapPlacesProvider implements PlacesProvider {
if (!secret) return null;
const signed = { ...params, key: this.credential.key, output: 'JSON' };
const canonical = Object.keys(signed)
.sort()
// Byte order, and it has to stay byte order: Amap computes the same signature over
// the same sorted names, so a locale-aware comparison would produce a signature the
// other side rejects.
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
.map((k) => `${k}=${signed[k]}`)
.join('&');
return createHash('md5').update(`${canonical}${secret}`).digest('hex');