list_widget: Fix unsafe unchecked casts in generic comparators.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-10-25 19:11:17 -07:00 committed by Tim Abbott
parent a53231a1ae
commit 2f95c55df4
2 changed files with 35 additions and 21 deletions

View File

@ -8,7 +8,6 @@ import * as blueslip from "./blueslip";
import * as scroll_util from "./scroll_util"; import * as scroll_util from "./scroll_util";
type SortingFunction<T> = (a: T, b: T) => number; type SortingFunction<T> = (a: T, b: T) => number;
type GenericSortingFunction<T = Record<string, unknown>> = (prop: string) => SortingFunction<T>;
type ListWidgetMeta<Key = unknown, Item = Key> = { type ListWidgetMeta<Key = unknown, Item = Key> = {
sorting_function: SortingFunction<Item> | null; sorting_function: SortingFunction<Item> | null;
@ -125,11 +124,13 @@ export function get_filtered_items<Key, Item>(
return result; return result;
} }
export const alphabetic_sort: GenericSortingFunction = (prop) => export function alphabetic_sort<Prop extends string>(
function (a, b) { prop: Prop,
): SortingFunction<Record<Prop, string>> {
return (a, b) => {
// The conversion to uppercase helps make the sorting case insensitive. // The conversion to uppercase helps make the sorting case insensitive.
const str1 = (a[prop] as string).toUpperCase(); const str1 = a[prop].toUpperCase();
const str2 = (b[prop] as string).toUpperCase(); const str2 = b[prop].toUpperCase();
if (str1 === str2) { if (str1 === str2) {
return 0; return 0;
@ -139,11 +140,14 @@ export const alphabetic_sort: GenericSortingFunction = (prop) =>
return -1; return -1;
}; };
}
export const numeric_sort: GenericSortingFunction = (prop) => export function numeric_sort<Prop extends string>(
function (a, b) { prop: Prop,
const a_prop = Number.parseFloat(a[prop] as string); ): SortingFunction<Record<Prop, number>> {
const b_prop = Number.parseFloat(b[prop] as string); return (a, b) => {
const a_prop = a[prop];
const b_prop = b[prop];
if (a_prop > b_prop) { if (a_prop > b_prop) {
return 1; return 1;
@ -153,23 +157,32 @@ export const numeric_sort: GenericSortingFunction = (prop) =>
return -1; return -1;
}; };
}
const generic_sorts = { type GenericSortKeys = {
alphabetic: string;
numeric: number;
};
const generic_sorts: {
[GenericFunc in keyof GenericSortKeys]: <Prop extends string>(
prop: Prop,
) => SortingFunction<Record<Prop, GenericSortKeys[GenericFunc]>>;
} = {
alphabetic: alphabetic_sort, alphabetic: alphabetic_sort,
numeric: numeric_sort, numeric: numeric_sort,
}; };
export function generic_sort_functions<T extends Record<string, unknown>>( export function generic_sort_functions<
generic_func: keyof typeof generic_sorts, GenericFunc extends keyof GenericSortKeys,
props: string[], Prop extends string,
): Record<string, SortingFunction<T>> { >(
const sorting_functions: Record<string, SortingFunction<T>> = {}; generic_func: GenericFunc,
for (const prop of props) { props: Prop[],
const key = `${prop}_${generic_func}`; ): Record<string, SortingFunction<Record<Prop, GenericSortKeys[GenericFunc]>>> {
sorting_functions[key] = generic_sorts[generic_func](prop); return Object.fromEntries(
} props.map((prop) => [`${prop}_${generic_func}`, generic_sorts[generic_func](prop)]),
);
return sorting_functions;
} }
function is_scroll_position_for_render(scroll_container: HTMLElement): boolean { function is_scroll_position_for_render(scroll_container: HTMLElement): boolean {

View File

@ -24,6 +24,7 @@ run_test("settings", ({override}) => {
override(list_widget, "create", (_$container, list) => { override(list_widget, "create", (_$container, list) => {
assert.deepEqual(list, [ assert.deepEqual(list, [
{ {
date_muted: 1577836800000,
date_muted_str: "Jan 1, 2020", date_muted_str: "Jan 1, 2020",
user_id: 5, user_id: 5,
user_name: "Feivel Fiverson", user_name: "Feivel Fiverson",