diff --git a/tsconfig.json b/tsconfig.json index 07dd0a313c..74ea340173 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,7 @@ /* Strict type-checking */ "strict": true, + "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "noImplicitOverride": true, diff --git a/web/debug-require-webpack-plugin.ts b/web/debug-require-webpack-plugin.ts index c3c8b8b6fc..d0233b2a9b 100644 --- a/web/debug-require-webpack-plugin.ts +++ b/web/debug-require-webpack-plugin.ts @@ -13,7 +13,7 @@ export default class DebugRequirePlugin implements WebpackPluginInstance { const resolved = new Map>(); const nameSymbol = Symbol("DebugRequirePluginName"); type NamedRequest = ResolveRequest & { - [nameSymbol]?: string; + [nameSymbol]?: string | undefined; }; let debugRequirePath: string | false = false; diff --git a/web/e2e-tests/lib/common.ts b/web/e2e-tests/lib/common.ts index 2244aa503f..79fff78a48 100644 --- a/web/e2e-tests/lib/common.ts +++ b/web/e2e-tests/lib/common.ts @@ -628,9 +628,9 @@ export async function run_test_async(test_function: (page: Page) => Promise => { let frame = new StackFrame({ - fileName: url, - lineNumber: lineNumber === undefined ? undefined : lineNumber + 1, - columnNumber: columnNumber === undefined ? undefined : columnNumber + 1, + ...(url !== undefined && {fileName: url}), + ...(lineNumber !== undefined && {lineNumber: lineNumber + 1}), + ...(columnNumber !== undefined && {columnNumber: columnNumber + 1}), }); try { frame = await gps.getMappedLocation(frame); diff --git a/web/src/blueslip_stacktrace.ts b/web/src/blueslip_stacktrace.ts index 38bdea376f..6c8706abb6 100644 --- a/web/src/blueslip_stacktrace.ts +++ b/web/src/blueslip_stacktrace.ts @@ -28,11 +28,11 @@ type NumberedLine = { }; type CleanStackFrame = { - full_path?: string; - show_path?: string; - function_name?: FunctionName; - line_number?: number; - context?: NumberedLine[]; + full_path: string | undefined; + show_path: string | undefined; + function_name: FunctionName | undefined; + line_number: number | undefined; + context: NumberedLine[] | undefined; }; export function exception_msg( diff --git a/web/src/bootstrap_typeahead.ts b/web/src/bootstrap_typeahead.ts index dee623a9f7..f0ae7117ed 100644 --- a/web/src/bootstrap_typeahead.ts +++ b/web/src/bootstrap_typeahead.ts @@ -215,7 +215,7 @@ export class Typeahead { dropup: boolean; automated: () => boolean; trigger_selection: (event: JQuery.KeyDownEvent) => boolean; - on_escape?: () => void; + on_escape: (() => void) | undefined; // returns a string to show in typeahead header or false. header_html: () => string | false; // returns a string to show in typeahead items or false. @@ -224,16 +224,16 @@ export class Typeahead { query = ""; mouse_moved_since_typeahead = false; shown = false; - openInputFieldOnKeyUp?: () => void; - closeInputFieldOnHide?: () => void; + openInputFieldOnKeyUp: (() => void) | undefined; + closeInputFieldOnHide: (() => void) | undefined; helpOnEmptyStrings: boolean; tabIsEnter: boolean; naturalSearch: boolean; stopAdvance: boolean; advanceKeyCodes: number[]; - parentElement?: string; + parentElement: string | undefined; values: WeakMap; - instance?: Instance; + instance: Instance | undefined; constructor(input_element: TypeaheadInputElement, options: TypeaheadOptions) { this.input_element = input_element; diff --git a/web/src/buddy_data.ts b/web/src/buddy_data.ts index 11fed91544..e071ed0074 100644 --- a/web/src/buddy_data.ts +++ b/web/src/buddy_data.ts @@ -204,7 +204,7 @@ export function get_title_data( is_group: boolean, ): { first_line: string; - second_line?: string; + second_line: string | undefined; third_line: string; show_you?: boolean; } { diff --git a/web/src/channel.ts b/web/src/channel.ts index e34b5c6f48..d5d914048c 100644 --- a/web/src/channel.ts +++ b/web/src/channel.ts @@ -14,17 +14,19 @@ import * as spectators from "./spectators"; type AjaxRequestHandlerOptions = Omit & { url: string; ignore_reload?: boolean; - success?: ( - data: unknown, - textStatus: JQuery.Ajax.SuccessTextStatus, - jqXHR: JQuery.jqXHR, - ) => void; + success?: + | (( + data: unknown, + textStatus: JQuery.Ajax.SuccessTextStatus, + jqXHR: JQuery.jqXHR, + ) => void) + | undefined; error?: JQuery.Ajax.ErrorCallback; }; type PatchRequestData = | {processData: false; data: FormData} - | {processData?: true; data: Record}; + | {processData?: true | undefined; data: Record}; export type AjaxRequestHandler = typeof call | typeof patch; diff --git a/web/src/compose_actions.ts b/web/src/compose_actions.ts index b6662cb993..3ba5686c36 100644 --- a/web/src/compose_actions.ts +++ b/web/src/compose_actions.ts @@ -33,14 +33,14 @@ type ComposeActionsStartOpts = { force_close?: boolean; trigger?: string; private_message_recipient?: string; - message?: Message; - stream_id?: number; + message?: Message | undefined; + stream_id?: number | undefined; topic?: string; content?: string; draft_id?: string; skip_scrolling_selected_message?: boolean; is_reply?: boolean; - keep_composebox_empty?: boolean; + keep_composebox_empty?: boolean | undefined; }; // An iteration on `ComposeActionsStartOpts` that enforces that diff --git a/web/src/compose_closed_ui.ts b/web/src/compose_closed_ui.ts index bc94fac7bf..974ad8ed44 100644 --- a/web/src/compose_closed_ui.ts +++ b/web/src/compose_closed_ui.ts @@ -18,9 +18,9 @@ function format_stream_recipient_label(stream_id: number, topic: string): string } type ComposeClosedMessage = { - stream_id?: number; + stream_id?: number | undefined; topic?: string; - display_reply_to?: string; + display_reply_to?: string | undefined; }; export function get_recipient_label(message?: ComposeClosedMessage): string { diff --git a/web/src/compose_reply.ts b/web/src/compose_reply.ts index 9cb1ce2b89..76030c5ac5 100644 --- a/web/src/compose_reply.ts +++ b/web/src/compose_reply.ts @@ -146,8 +146,8 @@ export function respond_to_message(opts: { message_type: msg_type, stream_id, topic, - private_message_recipient: pm_recipient, - trigger: opts.trigger, + ...(pm_recipient !== undefined && {private_message_recipient: pm_recipient}), + ...(opts.trigger !== undefined && {trigger: opts.trigger}), is_reply: true, keep_composebox_empty: opts.keep_composebox_empty, }); diff --git a/web/src/compose_ui.ts b/web/src/compose_ui.ts index 7ff5a16fa6..1cd705cc13 100644 --- a/web/src/compose_ui.ts +++ b/web/src/compose_ui.ts @@ -31,7 +31,7 @@ export type ComposeTriggeredOptions = { | { message_type: "stream"; topic: string; - stream_id?: number; + stream_id?: number | undefined; } | { message_type: "private"; diff --git a/web/src/drafts.ts b/web/src/drafts.ts index 7683e457b0..13488b068e 100644 --- a/web/src/drafts.ts +++ b/web/src/drafts.ts @@ -555,7 +555,7 @@ type FormattedDraft = | { is_stream: true; draft_id: string; - stream_name?: string; + stream_name?: string | undefined; recipient_bar_color: string; stream_privacy_icon_color: string; topic: string; diff --git a/web/src/dropdown_widget.ts b/web/src/dropdown_widget.ts index a99f57cdcc..68d8f87455 100644 --- a/web/src/dropdown_widget.ts +++ b/web/src/dropdown_widget.ts @@ -59,7 +59,7 @@ type DropdownWidgetOptions = { focus_target_on_hidden?: boolean; tippy_props?: Partial; // NOTE: Any value other than `undefined` will be rendered when class is initialized. - default_id?: string | number; + default_id?: string | number | undefined; unique_id_type?: DataTypes; // Text to show if the current value is not in `get_options()`. text_if_current_value_not_in_options?: string; diff --git a/web/src/filter.ts b/web/src/filter.ts index 7c3df5dad5..31b7209be4 100644 --- a/web/src/filter.ts +++ b/web/src/filter.ts @@ -243,7 +243,7 @@ function message_matches_search_term(message: Message, operator: string, operand export class Filter { _terms: NarrowTerm[]; - _sub?: StreamSubscription; + _sub?: StreamSubscription | undefined; _sorted_term_types?: string[] = undefined; _predicate?: (message: Message) => boolean; _can_mark_messages_read?: boolean; @@ -914,8 +914,8 @@ export class Filter { add_icon_data(context: { title: string; - description?: string; - link?: string; + description?: string | undefined; + link?: string | undefined; is_spectator: boolean; }): IconData { // We have special icons for the simple narrows available for the via sidebars. diff --git a/web/src/inbox_ui.ts b/web/src/inbox_ui.ts index 8b748b97bc..8cc69084cf 100644 --- a/web/src/inbox_ui.ts +++ b/web/src/inbox_ui.ts @@ -860,7 +860,7 @@ function update_closed_compose_text($row: JQuery, is_header_row: boolean): void compose_closed_ui.update_reply_recipient_label(message); } -export function get_focused_row_message(): {message?: Message} & ( +export function get_focused_row_message(): {message?: Message | undefined} & ( | {msg_type: "private"; private_message_recipient?: string} | {msg_type: "stream"; stream_id: number; topic?: string} | {msg_type?: never} @@ -893,6 +893,7 @@ export function get_focused_row_message(): {message?: Message} & ( const message = message_store.get(row_info.latest_msg_id); if (message === undefined) { const recipients = people.user_ids_string_to_emails_string(row_info.user_ids_string); + assert(recipients !== undefined); return { msg_type: "private", private_message_recipient: recipients, diff --git a/web/src/input_pill.ts b/web/src/input_pill.ts index ae5d4af91d..e5b5e8f18b 100644 --- a/web/src/input_pill.ts +++ b/web/src/input_pill.ts @@ -17,7 +17,7 @@ export type InputPillItem = { type: string; img_src?: string; deactivated?: boolean; - status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code?: boolean}; // TODO: Move this in user_status.js + status_emoji_info?: (EmojiRenderingDetails & {emoji_alt_code?: boolean}) | undefined; // TODO: Move this in user_status.js should_add_guest_user_indicator?: boolean; } & T; @@ -58,11 +58,11 @@ type InputPillStore = { type InputPillRenderingDetails = { display_value: string; has_image: boolean; - img_src?: string; - deactivated?: boolean; + img_src?: string | undefined; + deactivated: boolean | undefined; has_status?: boolean; - status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code?: boolean}; - should_add_guest_user_indicator?: boolean; + status_emoji_info?: (EmojiRenderingDetails & {emoji_alt_code?: boolean}) | undefined; + should_add_guest_user_indicator: boolean | undefined; }; // These are the functions that are exposed to other modules. diff --git a/web/src/list_cursor.ts b/web/src/list_cursor.ts index 99373abc8b..bb2b8cce9b 100644 --- a/web/src/list_cursor.ts +++ b/web/src/list_cursor.ts @@ -14,7 +14,7 @@ type List = { export class ListCursor { highlight_class: string; list: List; - curr_key?: Key; + curr_key?: Key | undefined; constructor({highlight_class, list}: {highlight_class: string; list: List}) { this.highlight_class = highlight_class; diff --git a/web/src/loading.ts b/web/src/loading.ts index 9b14b9737f..1d74935121 100644 --- a/web/src/loading.ts +++ b/web/src/loading.ts @@ -11,7 +11,12 @@ export function make_indicator( text, width, height, - }: {abs_positioned?: boolean; text?: string; width?: number; height?: number} = {}, + }: { + abs_positioned?: boolean; + text?: string; + width?: number | undefined; + height?: number | undefined; + } = {}, ): void { let $container = $outer_container; diff --git a/web/src/message_edit_history.ts b/web/src/message_edit_history.ts index 0da8ca661a..c0759802fc 100644 --- a/web/src/message_edit_history.ts +++ b/web/src/message_edit_history.ts @@ -30,15 +30,15 @@ type EditHistoryEntry = { edited_by_notice: string; timestamp: number; // require to set data-message-id for overlay message row is_stream: boolean; - recipient_bar_color?: string; - body_to_render?: string; - topic_edited?: boolean; - prev_topic?: string; - new_topic?: string; - stream_changed?: boolean; - prev_stream?: string; - prev_stream_id?: number; - new_stream?: string; + recipient_bar_color: string | undefined; + body_to_render: string | undefined; + topic_edited: boolean | undefined; + prev_topic: string | undefined; + new_topic: string | undefined; + stream_changed: boolean | undefined; + prev_stream: string | undefined; + prev_stream_id: number | undefined; + new_stream: string | undefined; }; const server_message_history_schema = z.object({ diff --git a/web/src/message_store.ts b/web/src/message_store.ts index 1b4037cb03..7b3e52f71c 100644 --- a/web/src/message_store.ts +++ b/web/src/message_store.ts @@ -7,8 +7,8 @@ import type {UserStatusEmojiInfo} from "./user_status"; const stored_messages = new Map(); export type MatchedMessage = { - match_content?: string; - match_subject?: string; + match_content?: string | undefined; + match_subject?: string | undefined; }; export type MessageReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji"; @@ -130,7 +130,7 @@ export type Message = ( flags?: string[]; small_avatar_url?: string; // Used in `message_avatar.hbs` - status_emoji_info?: UserStatusEmojiInfo; // Used in `message_body.hbs` + status_emoji_info?: UserStatusEmojiInfo | undefined; // Used in `message_body.hbs` } & ( | { type: "private"; diff --git a/web/src/modals.ts b/web/src/modals.ts index 9d7c31105f..6841a03f75 100644 --- a/web/src/modals.ts +++ b/web/src/modals.ts @@ -10,9 +10,9 @@ type Hook = () => void; export type ModalConfig = { autoremove?: boolean; on_show?: () => void; - on_shown?: () => void; - on_hide?: () => void; - on_hidden?: () => void; + on_shown?: (() => void) | undefined; + on_hide?: (() => void) | undefined; + on_hidden?: (() => void) | undefined; }; const pre_open_hooks: Hook[] = []; diff --git a/web/src/narrow_state.ts b/web/src/narrow_state.ts index d75b6d989f..8e486ab0b5 100644 --- a/web/src/narrow_state.ts +++ b/web/src/narrow_state.ts @@ -101,8 +101,9 @@ export function set_compose_defaults(): { } } - if (single.has("topic")) { - opts.topic = single.get("topic"); + const topic = single.get("topic"); + if (topic !== undefined) { + opts.topic = topic; } const private_message_recipient = single.get("dm"); diff --git a/web/src/people.ts b/web/src/people.ts index 828f1e7e7b..8bba9f2096 100644 --- a/web/src/people.ts +++ b/web/src/people.ts @@ -20,7 +20,7 @@ import * as util from "./util"; export type ProfileData = { value: string; - rendered_value?: string; + rendered_value?: string | undefined; }; export type User = { @@ -29,7 +29,7 @@ export type User = { email: string; full_name: string; // used for caching result of remove_diacritics. - name_with_diacritics_removed?: string; + name_with_diacritics_removed?: string | undefined; date_joined: string; is_active: boolean; is_owner: boolean; @@ -1489,7 +1489,6 @@ export function make_user(user_id: number, email: string, full_name: string): Us // We explicitly don't set `avatar_url` for fake person objects so that fallback code // will ask the server or compute a gravatar URL only once we need the avatar URL, // it's important for performance that we not hash every user's email to get gravatar URLs. - avatar_url: undefined, avatar_version: 0, timezone: "", date_joined: "", diff --git a/web/src/pm_list_data.ts b/web/src/pm_list_data.ts index 9e28b4e743..079d4536bf 100644 --- a/web/src/pm_list_data.ts +++ b/web/src/pm_list_data.ts @@ -38,8 +38,8 @@ type DisplayObject = { is_zero: boolean; is_active: boolean; url: string; - status_emoji_info?: UserStatusEmojiInfo; - user_circle_class?: string; + status_emoji_info: UserStatusEmojiInfo | undefined; + user_circle_class: string | undefined; is_group: boolean; is_bot: boolean; }; diff --git a/web/src/poll_widget.ts b/web/src/poll_widget.ts index 10dd4940b7..6533e5f795 100644 --- a/web/src/poll_widget.ts +++ b/web/src/poll_widget.ts @@ -19,8 +19,8 @@ import * as people from "./people"; export type Event = {sender_id: number; data: InboundData}; export type PollWidgetExtraData = { - question?: string; - options?: string[]; + question?: string | undefined; + options?: string[] | undefined; }; export function activate({ diff --git a/web/src/presence.ts b/web/src/presence.ts index 242d4b25b0..4fac916208 100644 --- a/web/src/presence.ts +++ b/web/src/presence.ts @@ -4,13 +4,13 @@ import {user_settings} from "./user_settings"; export type RawPresence = { server_timestamp: number; - active_timestamp?: number; - idle_timestamp?: number; + active_timestamp?: number | undefined; + idle_timestamp?: number | undefined; }; export type PresenceStatus = { status: "active" | "idle" | "offline"; - last_active?: number; + last_active?: number | undefined; }; export type PresenceInfoFromEvent = { diff --git a/web/src/settings_invites.ts b/web/src/settings_invites.ts index 545d00e11d..be1a095a90 100644 --- a/web/src/settings_invites.ts +++ b/web/src/settings_invites.ts @@ -39,7 +39,7 @@ export const invite_schema = z.intersection( ]), ); type Invite = z.output & { - invited_as_text?: string; + invited_as_text?: string | undefined; invited_absolute_time?: string; expiry_date_absolute_time?: string; is_admin?: boolean; diff --git a/web/src/stats/stats.ts b/web/src/stats/stats.ts index 5b0b12b2ad..889e47b1a6 100644 --- a/web/src/stats/stats.ts +++ b/web/src/stats/stats.ts @@ -704,10 +704,10 @@ function populate_messages_sent_by_client(raw_data: unknown): void { const layout: Partial = { width: 750, - height: undefined, // set in draw_plot() + // height set in draw_plot() margin: {l: 10, r: 10, b: 40, t: 10}, font: font_14pt, - xaxis: {range: undefined}, // set in draw_plot() + // xaxis set in draw_plot() yaxis: {showticklabels: false}, showlegend: false, }; @@ -813,7 +813,7 @@ function populate_messages_sent_by_client(raw_data: unknown): void { $("#id_messages_sent_by_client > div").removeClass("spinner"); const data_ = plot_data[user_button][time_button]; layout.height = layout.margin!.b! + data_.trace.x.length * 30; - layout.xaxis!.range = [0, Math.max(...data_.trace.x) * 1.3]; + layout.xaxis = {range: [0, Math.max(...data_.trace.x) * 1.3]}; void Plotly.newPlot( "id_messages_sent_by_client", [data_.trace, data_.trace_annotations], diff --git a/web/src/stream_list.ts b/web/src/stream_list.ts index e04deced4e..5c97259521 100644 --- a/web/src/stream_list.ts +++ b/web/src/stream_list.ts @@ -664,11 +664,11 @@ export function refresh_muted_or_unmuted_stream(sub: StreamSubscription): void { } export function get_sidebar_stream_topic_info(filter: Filter): { - stream_id?: number; + stream_id: number | undefined; topic_selected: boolean; } { const result: { - stream_id?: number; + stream_id: number | undefined; topic_selected: boolean; } = { stream_id: undefined, diff --git a/web/src/topic_list.ts b/web/src/topic_list.ts index ded244b774..53c83506be 100644 --- a/web/src/topic_list.ts +++ b/web/src/topic_list.ts @@ -141,7 +141,7 @@ export function spinner_li(): ListInfoNode { } export class TopicListWidget { - prior_dom?: vdom.Tag = undefined; + prior_dom: vdom.Tag | undefined = undefined; $parent_elem: JQuery; my_stream_id: number; diff --git a/web/src/typeahead_helper.ts b/web/src/typeahead_helper.ts index 5206f8bcd8..5c18d45f7f 100644 --- a/web/src/typeahead_helper.ts +++ b/web/src/typeahead_helper.ts @@ -97,12 +97,12 @@ type StreamData = { }; export function render_typeahead_item(args: { - primary?: string; + primary?: string | undefined; is_person?: boolean; img_src?: string; - status_emoji_info?: UserStatusEmojiInfo; + status_emoji_info?: UserStatusEmojiInfo | undefined; secondary?: string | null; - pronouns?: string; + pronouns?: string | undefined; is_user_group?: boolean; stream?: StreamData; is_unsubscribed?: boolean; @@ -443,10 +443,10 @@ export function sort_recipients