tsconfig: Enable exactOptionalPropertyTypes.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-05-15 20:05:29 -07:00 committed by Tim Abbott
parent 08a50cf74b
commit 507eb4913c
33 changed files with 94 additions and 85 deletions

View File

@ -22,6 +22,7 @@
/* Strict type-checking */ /* Strict type-checking */
"strict": true, "strict": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"isolatedModules": true, "isolatedModules": true,
"noImplicitOverride": true, "noImplicitOverride": true,

View File

@ -13,7 +13,7 @@ export default class DebugRequirePlugin implements WebpackPluginInstance {
const resolved = new Map<string, Set<string>>(); const resolved = new Map<string, Set<string>>();
const nameSymbol = Symbol("DebugRequirePluginName"); const nameSymbol = Symbol("DebugRequirePluginName");
type NamedRequest = ResolveRequest & { type NamedRequest = ResolveRequest & {
[nameSymbol]?: string; [nameSymbol]?: string | undefined;
}; };
let debugRequirePath: string | false = false; let debugRequirePath: string | false = false;

View File

@ -628,9 +628,9 @@ export async function run_test_async(test_function: (page: Page) => Promise<void
columnNumber, columnNumber,
}: ConsoleMessageLocation): Promise<string> => { }: ConsoleMessageLocation): Promise<string> => {
let frame = new StackFrame({ let frame = new StackFrame({
fileName: url, ...(url !== undefined && {fileName: url}),
lineNumber: lineNumber === undefined ? undefined : lineNumber + 1, ...(lineNumber !== undefined && {lineNumber: lineNumber + 1}),
columnNumber: columnNumber === undefined ? undefined : columnNumber + 1, ...(columnNumber !== undefined && {columnNumber: columnNumber + 1}),
}); });
try { try {
frame = await gps.getMappedLocation(frame); frame = await gps.getMappedLocation(frame);

View File

@ -28,11 +28,11 @@ type NumberedLine = {
}; };
type CleanStackFrame = { type CleanStackFrame = {
full_path?: string; full_path: string | undefined;
show_path?: string; show_path: string | undefined;
function_name?: FunctionName; function_name: FunctionName | undefined;
line_number?: number; line_number: number | undefined;
context?: NumberedLine[]; context: NumberedLine[] | undefined;
}; };
export function exception_msg( export function exception_msg(

View File

@ -215,7 +215,7 @@ export class Typeahead<ItemType extends string | object> {
dropup: boolean; dropup: boolean;
automated: () => boolean; automated: () => boolean;
trigger_selection: (event: JQuery.KeyDownEvent) => boolean; trigger_selection: (event: JQuery.KeyDownEvent) => boolean;
on_escape?: () => void; on_escape: (() => void) | undefined;
// returns a string to show in typeahead header or false. // returns a string to show in typeahead header or false.
header_html: () => string | false; header_html: () => string | false;
// returns a string to show in typeahead items or false. // returns a string to show in typeahead items or false.
@ -224,16 +224,16 @@ export class Typeahead<ItemType extends string | object> {
query = ""; query = "";
mouse_moved_since_typeahead = false; mouse_moved_since_typeahead = false;
shown = false; shown = false;
openInputFieldOnKeyUp?: () => void; openInputFieldOnKeyUp: (() => void) | undefined;
closeInputFieldOnHide?: () => void; closeInputFieldOnHide: (() => void) | undefined;
helpOnEmptyStrings: boolean; helpOnEmptyStrings: boolean;
tabIsEnter: boolean; tabIsEnter: boolean;
naturalSearch: boolean; naturalSearch: boolean;
stopAdvance: boolean; stopAdvance: boolean;
advanceKeyCodes: number[]; advanceKeyCodes: number[];
parentElement?: string; parentElement: string | undefined;
values: WeakMap<HTMLElement, ItemType>; values: WeakMap<HTMLElement, ItemType>;
instance?: Instance; instance: Instance | undefined;
constructor(input_element: TypeaheadInputElement, options: TypeaheadOptions<ItemType>) { constructor(input_element: TypeaheadInputElement, options: TypeaheadOptions<ItemType>) {
this.input_element = input_element; this.input_element = input_element;

View File

@ -204,7 +204,7 @@ export function get_title_data(
is_group: boolean, is_group: boolean,
): { ): {
first_line: string; first_line: string;
second_line?: string; second_line: string | undefined;
third_line: string; third_line: string;
show_you?: boolean; show_you?: boolean;
} { } {

View File

@ -14,17 +14,19 @@ import * as spectators from "./spectators";
type AjaxRequestHandlerOptions = Omit<JQuery.AjaxSettings, "success"> & { type AjaxRequestHandlerOptions = Omit<JQuery.AjaxSettings, "success"> & {
url: string; url: string;
ignore_reload?: boolean; ignore_reload?: boolean;
success?: ( success?:
data: unknown, | ((
textStatus: JQuery.Ajax.SuccessTextStatus, data: unknown,
jqXHR: JQuery.jqXHR<unknown>, textStatus: JQuery.Ajax.SuccessTextStatus,
) => void; jqXHR: JQuery.jqXHR<unknown>,
) => void)
| undefined;
error?: JQuery.Ajax.ErrorCallback<unknown>; error?: JQuery.Ajax.ErrorCallback<unknown>;
}; };
type PatchRequestData = type PatchRequestData =
| {processData: false; data: FormData} | {processData: false; data: FormData}
| {processData?: true; data: Record<string, unknown>}; | {processData?: true | undefined; data: Record<string, unknown>};
export type AjaxRequestHandler = typeof call | typeof patch; export type AjaxRequestHandler = typeof call | typeof patch;

View File

@ -33,14 +33,14 @@ type ComposeActionsStartOpts = {
force_close?: boolean; force_close?: boolean;
trigger?: string; trigger?: string;
private_message_recipient?: string; private_message_recipient?: string;
message?: Message; message?: Message | undefined;
stream_id?: number; stream_id?: number | undefined;
topic?: string; topic?: string;
content?: string; content?: string;
draft_id?: string; draft_id?: string;
skip_scrolling_selected_message?: boolean; skip_scrolling_selected_message?: boolean;
is_reply?: boolean; is_reply?: boolean;
keep_composebox_empty?: boolean; keep_composebox_empty?: boolean | undefined;
}; };
// An iteration on `ComposeActionsStartOpts` that enforces that // An iteration on `ComposeActionsStartOpts` that enforces that

View File

@ -18,9 +18,9 @@ function format_stream_recipient_label(stream_id: number, topic: string): string
} }
type ComposeClosedMessage = { type ComposeClosedMessage = {
stream_id?: number; stream_id?: number | undefined;
topic?: string; topic?: string;
display_reply_to?: string; display_reply_to?: string | undefined;
}; };
export function get_recipient_label(message?: ComposeClosedMessage): string { export function get_recipient_label(message?: ComposeClosedMessage): string {

View File

@ -146,8 +146,8 @@ export function respond_to_message(opts: {
message_type: msg_type, message_type: msg_type,
stream_id, stream_id,
topic, topic,
private_message_recipient: pm_recipient, ...(pm_recipient !== undefined && {private_message_recipient: pm_recipient}),
trigger: opts.trigger, ...(opts.trigger !== undefined && {trigger: opts.trigger}),
is_reply: true, is_reply: true,
keep_composebox_empty: opts.keep_composebox_empty, keep_composebox_empty: opts.keep_composebox_empty,
}); });

View File

@ -31,7 +31,7 @@ export type ComposeTriggeredOptions = {
| { | {
message_type: "stream"; message_type: "stream";
topic: string; topic: string;
stream_id?: number; stream_id?: number | undefined;
} }
| { | {
message_type: "private"; message_type: "private";

View File

@ -555,7 +555,7 @@ type FormattedDraft =
| { | {
is_stream: true; is_stream: true;
draft_id: string; draft_id: string;
stream_name?: string; stream_name?: string | undefined;
recipient_bar_color: string; recipient_bar_color: string;
stream_privacy_icon_color: string; stream_privacy_icon_color: string;
topic: string; topic: string;

View File

@ -59,7 +59,7 @@ type DropdownWidgetOptions = {
focus_target_on_hidden?: boolean; focus_target_on_hidden?: boolean;
tippy_props?: Partial<tippy.Props>; tippy_props?: Partial<tippy.Props>;
// NOTE: Any value other than `undefined` will be rendered when class is initialized. // 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; unique_id_type?: DataTypes;
// Text to show if the current value is not in `get_options()`. // Text to show if the current value is not in `get_options()`.
text_if_current_value_not_in_options?: string; text_if_current_value_not_in_options?: string;

View File

@ -243,7 +243,7 @@ function message_matches_search_term(message: Message, operator: string, operand
export class Filter { export class Filter {
_terms: NarrowTerm[]; _terms: NarrowTerm[];
_sub?: StreamSubscription; _sub?: StreamSubscription | undefined;
_sorted_term_types?: string[] = undefined; _sorted_term_types?: string[] = undefined;
_predicate?: (message: Message) => boolean; _predicate?: (message: Message) => boolean;
_can_mark_messages_read?: boolean; _can_mark_messages_read?: boolean;
@ -914,8 +914,8 @@ export class Filter {
add_icon_data(context: { add_icon_data(context: {
title: string; title: string;
description?: string; description?: string | undefined;
link?: string; link?: string | undefined;
is_spectator: boolean; is_spectator: boolean;
}): IconData { }): IconData {
// We have special icons for the simple narrows available for the via sidebars. // We have special icons for the simple narrows available for the via sidebars.

View File

@ -860,7 +860,7 @@ function update_closed_compose_text($row: JQuery, is_header_row: boolean): void
compose_closed_ui.update_reply_recipient_label(message); 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: "private"; private_message_recipient?: string}
| {msg_type: "stream"; stream_id: number; topic?: string} | {msg_type: "stream"; stream_id: number; topic?: string}
| {msg_type?: never} | {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); const message = message_store.get(row_info.latest_msg_id);
if (message === undefined) { if (message === undefined) {
const recipients = people.user_ids_string_to_emails_string(row_info.user_ids_string); const recipients = people.user_ids_string_to_emails_string(row_info.user_ids_string);
assert(recipients !== undefined);
return { return {
msg_type: "private", msg_type: "private",
private_message_recipient: recipients, private_message_recipient: recipients,

View File

@ -17,7 +17,7 @@ export type InputPillItem<T> = {
type: string; type: string;
img_src?: string; img_src?: string;
deactivated?: boolean; 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; should_add_guest_user_indicator?: boolean;
} & T; } & T;
@ -58,11 +58,11 @@ type InputPillStore<T> = {
type InputPillRenderingDetails = { type InputPillRenderingDetails = {
display_value: string; display_value: string;
has_image: boolean; has_image: boolean;
img_src?: string; img_src?: string | undefined;
deactivated?: boolean; deactivated: boolean | undefined;
has_status?: boolean; has_status?: boolean;
status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code?: boolean}; status_emoji_info?: (EmojiRenderingDetails & {emoji_alt_code?: boolean}) | undefined;
should_add_guest_user_indicator?: boolean; should_add_guest_user_indicator: boolean | undefined;
}; };
// These are the functions that are exposed to other modules. // These are the functions that are exposed to other modules.

View File

@ -14,7 +14,7 @@ type List<Key> = {
export class ListCursor<Key> { export class ListCursor<Key> {
highlight_class: string; highlight_class: string;
list: List<Key>; list: List<Key>;
curr_key?: Key; curr_key?: Key | undefined;
constructor({highlight_class, list}: {highlight_class: string; list: List<Key>}) { constructor({highlight_class, list}: {highlight_class: string; list: List<Key>}) {
this.highlight_class = highlight_class; this.highlight_class = highlight_class;

View File

@ -11,7 +11,12 @@ export function make_indicator(
text, text,
width, width,
height, height,
}: {abs_positioned?: boolean; text?: string; width?: number; height?: number} = {}, }: {
abs_positioned?: boolean;
text?: string;
width?: number | undefined;
height?: number | undefined;
} = {},
): void { ): void {
let $container = $outer_container; let $container = $outer_container;

View File

@ -30,15 +30,15 @@ type EditHistoryEntry = {
edited_by_notice: string; edited_by_notice: string;
timestamp: number; // require to set data-message-id for overlay message row timestamp: number; // require to set data-message-id for overlay message row
is_stream: boolean; is_stream: boolean;
recipient_bar_color?: string; recipient_bar_color: string | undefined;
body_to_render?: string; body_to_render: string | undefined;
topic_edited?: boolean; topic_edited: boolean | undefined;
prev_topic?: string; prev_topic: string | undefined;
new_topic?: string; new_topic: string | undefined;
stream_changed?: boolean; stream_changed: boolean | undefined;
prev_stream?: string; prev_stream: string | undefined;
prev_stream_id?: number; prev_stream_id: number | undefined;
new_stream?: string; new_stream: string | undefined;
}; };
const server_message_history_schema = z.object({ const server_message_history_schema = z.object({

View File

@ -7,8 +7,8 @@ import type {UserStatusEmojiInfo} from "./user_status";
const stored_messages = new Map<number, Message>(); const stored_messages = new Map<number, Message>();
export type MatchedMessage = { export type MatchedMessage = {
match_content?: string; match_content?: string | undefined;
match_subject?: string; match_subject?: string | undefined;
}; };
export type MessageReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji"; export type MessageReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji";
@ -130,7 +130,7 @@ export type Message = (
flags?: string[]; flags?: string[];
small_avatar_url?: string; // Used in `message_avatar.hbs` 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"; type: "private";

View File

@ -10,9 +10,9 @@ type Hook = () => void;
export type ModalConfig = { export type ModalConfig = {
autoremove?: boolean; autoremove?: boolean;
on_show?: () => void; on_show?: () => void;
on_shown?: () => void; on_shown?: (() => void) | undefined;
on_hide?: () => void; on_hide?: (() => void) | undefined;
on_hidden?: () => void; on_hidden?: (() => void) | undefined;
}; };
const pre_open_hooks: Hook[] = []; const pre_open_hooks: Hook[] = [];

View File

@ -101,8 +101,9 @@ export function set_compose_defaults(): {
} }
} }
if (single.has("topic")) { const topic = single.get("topic");
opts.topic = single.get("topic"); if (topic !== undefined) {
opts.topic = topic;
} }
const private_message_recipient = single.get("dm"); const private_message_recipient = single.get("dm");

View File

@ -20,7 +20,7 @@ import * as util from "./util";
export type ProfileData = { export type ProfileData = {
value: string; value: string;
rendered_value?: string; rendered_value?: string | undefined;
}; };
export type User = { export type User = {
@ -29,7 +29,7 @@ export type User = {
email: string; email: string;
full_name: string; full_name: string;
// used for caching result of remove_diacritics. // used for caching result of remove_diacritics.
name_with_diacritics_removed?: string; name_with_diacritics_removed?: string | undefined;
date_joined: string; date_joined: string;
is_active: boolean; is_active: boolean;
is_owner: 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 // 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, // 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. // it's important for performance that we not hash every user's email to get gravatar URLs.
avatar_url: undefined,
avatar_version: 0, avatar_version: 0,
timezone: "", timezone: "",
date_joined: "", date_joined: "",

View File

@ -38,8 +38,8 @@ type DisplayObject = {
is_zero: boolean; is_zero: boolean;
is_active: boolean; is_active: boolean;
url: string; url: string;
status_emoji_info?: UserStatusEmojiInfo; status_emoji_info: UserStatusEmojiInfo | undefined;
user_circle_class?: string; user_circle_class: string | undefined;
is_group: boolean; is_group: boolean;
is_bot: boolean; is_bot: boolean;
}; };

View File

@ -19,8 +19,8 @@ import * as people from "./people";
export type Event = {sender_id: number; data: InboundData}; export type Event = {sender_id: number; data: InboundData};
export type PollWidgetExtraData = { export type PollWidgetExtraData = {
question?: string; question?: string | undefined;
options?: string[]; options?: string[] | undefined;
}; };
export function activate({ export function activate({

View File

@ -4,13 +4,13 @@ import {user_settings} from "./user_settings";
export type RawPresence = { export type RawPresence = {
server_timestamp: number; server_timestamp: number;
active_timestamp?: number; active_timestamp?: number | undefined;
idle_timestamp?: number; idle_timestamp?: number | undefined;
}; };
export type PresenceStatus = { export type PresenceStatus = {
status: "active" | "idle" | "offline"; status: "active" | "idle" | "offline";
last_active?: number; last_active?: number | undefined;
}; };
export type PresenceInfoFromEvent = { export type PresenceInfoFromEvent = {

View File

@ -39,7 +39,7 @@ export const invite_schema = z.intersection(
]), ]),
); );
type Invite = z.output<typeof invite_schema> & { type Invite = z.output<typeof invite_schema> & {
invited_as_text?: string; invited_as_text?: string | undefined;
invited_absolute_time?: string; invited_absolute_time?: string;
expiry_date_absolute_time?: string; expiry_date_absolute_time?: string;
is_admin?: boolean; is_admin?: boolean;

View File

@ -704,10 +704,10 @@ function populate_messages_sent_by_client(raw_data: unknown): void {
const layout: Partial<Plotly.Layout> = { const layout: Partial<Plotly.Layout> = {
width: 750, width: 750,
height: undefined, // set in draw_plot() // height set in draw_plot()
margin: {l: 10, r: 10, b: 40, t: 10}, margin: {l: 10, r: 10, b: 40, t: 10},
font: font_14pt, font: font_14pt,
xaxis: {range: undefined}, // set in draw_plot() // xaxis set in draw_plot()
yaxis: {showticklabels: false}, yaxis: {showticklabels: false},
showlegend: 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"); $("#id_messages_sent_by_client > div").removeClass("spinner");
const data_ = plot_data[user_button][time_button]; const data_ = plot_data[user_button][time_button];
layout.height = layout.margin!.b! + data_.trace.x.length * 30; 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( void Plotly.newPlot(
"id_messages_sent_by_client", "id_messages_sent_by_client",
[data_.trace, data_.trace_annotations], [data_.trace, data_.trace_annotations],

View File

@ -664,11 +664,11 @@ export function refresh_muted_or_unmuted_stream(sub: StreamSubscription): void {
} }
export function get_sidebar_stream_topic_info(filter: Filter): { export function get_sidebar_stream_topic_info(filter: Filter): {
stream_id?: number; stream_id: number | undefined;
topic_selected: boolean; topic_selected: boolean;
} { } {
const result: { const result: {
stream_id?: number; stream_id: number | undefined;
topic_selected: boolean; topic_selected: boolean;
} = { } = {
stream_id: undefined, stream_id: undefined,

View File

@ -141,7 +141,7 @@ export function spinner_li(): ListInfoNode {
} }
export class TopicListWidget { export class TopicListWidget {
prior_dom?: vdom.Tag<ListInfoNodeOptions> = undefined; prior_dom: vdom.Tag<ListInfoNodeOptions> | undefined = undefined;
$parent_elem: JQuery; $parent_elem: JQuery;
my_stream_id: number; my_stream_id: number;

View File

@ -97,12 +97,12 @@ type StreamData = {
}; };
export function render_typeahead_item(args: { export function render_typeahead_item(args: {
primary?: string; primary?: string | undefined;
is_person?: boolean; is_person?: boolean;
img_src?: string; img_src?: string;
status_emoji_info?: UserStatusEmojiInfo; status_emoji_info?: UserStatusEmojiInfo | undefined;
secondary?: string | null; secondary?: string | null;
pronouns?: string; pronouns?: string | undefined;
is_user_group?: boolean; is_user_group?: boolean;
stream?: StreamData; stream?: StreamData;
is_unsubscribed?: boolean; is_unsubscribed?: boolean;
@ -443,10 +443,10 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
}: { }: {
users: UserType[]; users: UserType[];
query: string; query: string;
current_stream_id?: number; current_stream_id?: number | undefined;
current_topic?: string; current_topic?: string | undefined;
groups?: UserGroupPillData[]; groups?: UserGroupPillData[];
max_num_items?: number; max_num_items?: number | undefined;
}): (UserType | UserGroupPillData)[] { }): (UserType | UserGroupPillData)[] {
function sort_relevance(items: UserType[]): UserType[] { function sort_relevance(items: UserType[]): UserType[] {
return sort_people_for_relevance(items, current_stream_id, current_topic); return sort_people_for_relevance(items, current_stream_id, current_topic);

View File

@ -74,7 +74,7 @@ export function extract_pm_recipients(recipients: string): string[] {
// When the type is "private", properties from to_user_ids might be undefined. // When the type is "private", properties from to_user_ids might be undefined.
// See https://github.com/zulip/zulip/pull/23032#discussion_r1038480596. // See https://github.com/zulip/zulip/pull/23032#discussion_r1038480596.
export type Recipient = export type Recipient =
| {type: "private"; to_user_ids?: string; reply_to: string} | {type: "private"; to_user_ids?: string | undefined; reply_to: string}
| ({type: "stream"} & StreamTopic); | ({type: "stream"} & StreamTopic);
export const same_recipient = function util_same_recipient(a?: Recipient, b?: Recipient): boolean { export const same_recipient = function util_same_recipient(a?: Recipient, b?: Recipient): boolean {

View File

@ -14,8 +14,8 @@ type ZFormExtraData = {
// TODO: This TodoWidgetExtraData type should be moved to web/src/todo_widget.js when it will be migrated // TODO: This TodoWidgetExtraData type should be moved to web/src/todo_widget.js when it will be migrated
type TodoWidgetExtraData = { type TodoWidgetExtraData = {
task_list_title?: string; task_list_title?: string | undefined;
tasks?: {task: string; desc: string}[]; tasks?: {task: string; desc: string}[] | undefined;
}; };
type WidgetExtraData = PollWidgetExtraData | TodoWidgetExtraData | ZFormExtraData | null; type WidgetExtraData = PollWidgetExtraData | TodoWidgetExtraData | ZFormExtraData | null;