From c862c673af25022b9f2fb7eb9dbc3928499e84bf Mon Sep 17 00:00:00 2001 From: Priyank Patel Date: Sun, 6 Jun 2021 20:13:18 +0000 Subject: [PATCH] ts: Convert blueslip module to TypeScript. We turn off the eslint no-use-before-define for TypeScript files because it does not work correctly for types. There is already a typescript-eslint version of it that is enabled for TS. We also update the error handler on window to use instanceof check for ErrorEvent instead of checking the error property. --- .eslintrc.json | 1 + static/js/{blueslip.js => blueslip.ts} | 63 +++++++++++++++----------- static/js/global.d.ts | 1 + static/js/page_params.ts | 2 + tools/linter_lib/custom_check.py | 2 +- tools/test-js-with-node | 2 +- 6 files changed, 43 insertions(+), 28 deletions(-) rename static/js/{blueslip.js => blueslip.ts} (83%) diff --git a/.eslintrc.json b/.eslintrc.json index 4b4fdb2af9..e9f1148132 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -145,6 +145,7 @@ "no-duplicate-imports": "off", "no-unused-vars": "off", "no-useless-constructor": "off", + "no-use-before-define": "off", "@typescript-eslint/array-type": "error", "@typescript-eslint/await-thenable": "error", diff --git a/static/js/blueslip.js b/static/js/blueslip.ts similarity index 83% rename from static/js/blueslip.js rename to static/js/blueslip.ts index b3ef413309..bfc2b1fce1 100644 --- a/static/js/blueslip.js +++ b/static/js/blueslip.ts @@ -16,12 +16,12 @@ if (Error.stackTraceLimit !== undefined) { Error.stackTraceLimit = 100000; } -function pad(num, width) { +function pad(num: number, width: number): string { return num.toString().padStart(width, "0"); } -function make_logger_func(name) { - return function Logger_func(...args) { +function make_logger_func(name: "debug" | "log" | "info" | "warn" | "error") { + return function Logger_func(this: Logger, ...args: unknown[]) { const now = new Date(); const date_str = now.getUTCFullYear() + @@ -62,27 +62,26 @@ class Logger { warn = make_logger_func("warn"); error = make_logger_func("error"); - _memory_log = []; - - get_log() { + _memory_log: string[] = []; + get_log(): string[] { return this._memory_log; } } const logger = new Logger(); -export function get_log() { +export function get_log(): string[] { return logger.get_log(); } -const reported_errors = new Set(); -const last_report_attempt = new Map(); +const reported_errors = new Set(); +const last_report_attempt = new Map(); function report_error( - msg, + msg: string, stack = "No stacktrace available", - {show_ui_msg = false, more_info} = {}, -) { + {show_ui_msg = false, more_info}: {show_ui_msg?: boolean; more_info?: unknown} = {}, +): void { if (page_params.development_environment) { // In development, we display blueslip errors in the web UI, // to make them hard to miss. @@ -176,8 +175,8 @@ function report_error( class BlueslipError extends Error { name = "BlueslipError"; - - constructor(msg, more_info) { + more_info?: unknown; + constructor(msg: string, more_info: unknown) { super(msg); if (more_info !== undefined) { this.more_info = more_info; @@ -185,7 +184,13 @@ class BlueslipError extends Error { } } -export function exception_msg(ex) { +export function exception_msg( + ex: Error & { + // Unsupported properties avaliable on some browsers + fileName?: string; + lineNumber?: number; + }, +): string { let message = ex.message; if (ex.fileName !== undefined) { message += " at " + ex.fileName; @@ -197,38 +202,44 @@ export function exception_msg(ex) { } $(window).on("error", (event) => { - const ex = event.originalEvent.error; + const {originalEvent} = event; + if (!(originalEvent instanceof ErrorEvent)) { + return; + } + + const ex = originalEvent.error; if (!ex || ex instanceof BlueslipError) { return; } + const message = exception_msg(ex); report_error(message, ex.stack); }); -function build_arg_list(msg, more_info) { - const args = [msg]; +function build_arg_list(msg: string, more_info: unknown): [string, string?, unknown?] { + const args: [string, string?, unknown?] = [msg]; if (more_info !== undefined) { args.push("\nAdditional information: ", more_info); } return args; } -export function debug(msg, more_info) { +export function debug(msg: string, more_info: unknown): void { const args = build_arg_list(msg, more_info); logger.debug(...args); } -export function log(msg, more_info) { +export function log(msg: string, more_info: unknown): void { const args = build_arg_list(msg, more_info); logger.log(...args); } -export function info(msg, more_info) { +export function info(msg: string, more_info: unknown): void { const args = build_arg_list(msg, more_info); logger.info(...args); } -export function warn(msg, more_info) { +export function warn(msg: string, more_info: unknown): void { const args = build_arg_list(msg, more_info); logger.warn(...args); if (page_params.development_environment) { @@ -236,7 +247,7 @@ export function warn(msg, more_info) { } } -export function error(msg, more_info, stack = new Error("dummy").stack) { +export function error(msg: string, more_info: unknown, stack = new Error("dummy").stack): void { const args = build_arg_list(msg, more_info); logger.error(...args); report_error(msg, stack, {more_info}); @@ -251,7 +262,7 @@ export function error(msg, more_info, stack = new Error("dummy").stack) { export const timings = new Map(); -export function measure_time(label, f) { +export function measure_time(label: string, f: () => T): T { const t1 = performance.now(); const ret = f(); const t2 = performance.now(); @@ -264,8 +275,8 @@ export function measure_time(label, f) { // only used for including in error report emails; be sure to discuss // with other developers before using it in a user-facing context // because it is not XSS-safe. -export function preview_node(node) { - if (node instanceof $) { +export function preview_node(node: JQuery | HTMLElement): string { + if (!(node instanceof HTMLElement)) { node = node[0]; } diff --git a/static/js/global.d.ts b/static/js/global.d.ts index 8e44d286fc..a4fefd55a9 100644 --- a/static/js/global.d.ts +++ b/static/js/global.d.ts @@ -10,5 +10,6 @@ interface JQuery { } interface Window { + last_stacktrace: string; page_params_parse_time: number; } diff --git a/static/js/page_params.ts b/static/js/page_params.ts index 11ef206fd8..6ebc9d41f2 100644 --- a/static/js/page_params.ts +++ b/static/js/page_params.ts @@ -8,7 +8,9 @@ export const page_params: { name: string; percent_translated: number | undefined; }[]; + development_environment: boolean; request_language: string; + save_stacktraces: boolean; translation_data: Record; } = $("#page-params").remove().data("params"); const t2 = performance.now(); diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index 779c7c79e0..7cde8e0e1f 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -190,7 +190,7 @@ js_rules = RuleList( "description": "Use channel module for AJAX calls", "exclude": { # Internal modules can do direct network calls - "static/js/blueslip.js", + "static/js/blueslip.ts", "static/js/channel.js", # External modules that don't include channel.js "static/js/stats/", diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 1187d4fb0c..15aabe02c4 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -42,7 +42,7 @@ EXEMPT_FILES = { "static/js/attachments_ui.js", "static/js/avatar.js", "static/js/billing/helpers.js", - "static/js/blueslip.js", + "static/js/blueslip.ts", "static/js/blueslip_stacktrace.ts", "static/js/click_handlers.js", "static/js/compose_actions.js",