From 2f95c55df468551cdd97dde5470bfdf22f91dbdd Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Wed, 25 Oct 2023 19:11:17 -0700 Subject: [PATCH] list_widget: Fix unsafe unchecked casts in generic comparators. Signed-off-by: Anders Kaseorg --- web/src/list_widget.ts | 55 ++++++++++++++++---------- web/tests/settings_muted_users.test.js | 1 + 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/web/src/list_widget.ts b/web/src/list_widget.ts index 34f0d8207f..f793c747d7 100644 --- a/web/src/list_widget.ts +++ b/web/src/list_widget.ts @@ -8,7 +8,6 @@ import * as blueslip from "./blueslip"; import * as scroll_util from "./scroll_util"; type SortingFunction = (a: T, b: T) => number; -type GenericSortingFunction> = (prop: string) => SortingFunction; type ListWidgetMeta = { sorting_function: SortingFunction | null; @@ -125,11 +124,13 @@ export function get_filtered_items( return result; } -export const alphabetic_sort: GenericSortingFunction = (prop) => - function (a, b) { +export function alphabetic_sort( + prop: Prop, +): SortingFunction> { + return (a, b) => { // The conversion to uppercase helps make the sorting case insensitive. - const str1 = (a[prop] as string).toUpperCase(); - const str2 = (b[prop] as string).toUpperCase(); + const str1 = a[prop].toUpperCase(); + const str2 = b[prop].toUpperCase(); if (str1 === str2) { return 0; @@ -139,11 +140,14 @@ export const alphabetic_sort: GenericSortingFunction = (prop) => return -1; }; +} -export const numeric_sort: GenericSortingFunction = (prop) => - function (a, b) { - const a_prop = Number.parseFloat(a[prop] as string); - const b_prop = Number.parseFloat(b[prop] as string); +export function numeric_sort( + prop: Prop, +): SortingFunction> { + return (a, b) => { + const a_prop = a[prop]; + const b_prop = b[prop]; if (a_prop > b_prop) { return 1; @@ -153,23 +157,32 @@ export const numeric_sort: GenericSortingFunction = (prop) => return -1; }; +} -const generic_sorts = { +type GenericSortKeys = { + alphabetic: string; + numeric: number; +}; + +const generic_sorts: { + [GenericFunc in keyof GenericSortKeys]: ( + prop: Prop, + ) => SortingFunction>; +} = { alphabetic: alphabetic_sort, numeric: numeric_sort, }; -export function generic_sort_functions>( - generic_func: keyof typeof generic_sorts, - props: string[], -): Record> { - const sorting_functions: Record> = {}; - for (const prop of props) { - const key = `${prop}_${generic_func}`; - sorting_functions[key] = generic_sorts[generic_func](prop); - } - - return sorting_functions; +export function generic_sort_functions< + GenericFunc extends keyof GenericSortKeys, + Prop extends string, +>( + generic_func: GenericFunc, + props: Prop[], +): Record>> { + return Object.fromEntries( + props.map((prop) => [`${prop}_${generic_func}`, generic_sorts[generic_func](prop)]), + ); } function is_scroll_position_for_render(scroll_container: HTMLElement): boolean { diff --git a/web/tests/settings_muted_users.test.js b/web/tests/settings_muted_users.test.js index 48272a2f50..ec78937e02 100644 --- a/web/tests/settings_muted_users.test.js +++ b/web/tests/settings_muted_users.test.js @@ -24,6 +24,7 @@ run_test("settings", ({override}) => { override(list_widget, "create", (_$container, list) => { assert.deepEqual(list, [ { + date_muted: 1577836800000, date_muted_str: "Jan 1, 2020", user_id: 5, user_name: "Feivel Fiverson",