diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-12-06 03:53:16 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-06 03:53:16 +0000 | 
| commit | bd5bc1a5db29903bc098995cd9262c4576bf76af (patch) | |
| tree | c9214189e0214480fcf6539ad1c6327aef6cbd1c /ext/js/core.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
| parent | 23e6fb76319c9ed7c9bcdc3efba39bc5dd38f288 (diff) | |
Merge pull request #339 from toasted-nutbread/type-annotations
Type annotations
Diffstat (limited to 'ext/js/core.js')
| -rw-r--r-- | ext/js/core.js | 345 | 
1 files changed, 173 insertions, 172 deletions
diff --git a/ext/js/core.js b/ext/js/core.js index fb164795..5c03b44b 100644 --- a/ext/js/core.js +++ b/ext/js/core.js @@ -16,54 +16,11 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -/** - * Converts an `Error` object to a serializable JSON object. - * @param {*} error An error object to convert. - * @returns {{name: string, message: string, stack: string, data?: *}|{value: *, hasValue: boolean}} A simple object which can be serialized by `JSON.stringify()`. - */ -export function serializeError(error) { -    try { -        if (typeof error === 'object' && error !== null) { -            const result = { -                name: error.name, -                message: error.message, -                stack: error.stack -            }; -            if (Object.prototype.hasOwnProperty.call(error, 'data')) { -                result.data = error.data; -            } -            return result; -        } -    } catch (e) { -        // NOP -    } -    return { -        value: error, -        hasValue: true -    }; -} - -/** - * Converts a serialized erorr into a standard `Error` object. - * @param {{name: string, message: string, stack: string, data?: *}|{value: *, hasValue: boolean}} serializedError A simple object which was initially generated by serializeError. - * @returns {Error|*} A new `Error` instance. - */ -export function deserializeError(serializedError) { -    if (serializedError.hasValue) { -        return serializedError.value; -    } -    const error = new Error(serializedError.message); -    error.name = serializedError.name; -    error.stack = serializedError.stack; -    if (Object.prototype.hasOwnProperty.call(serializedError, 'data')) { -        error.data = serializedError.data; -    } -    return error; -} +import {ExtensionError} from './core/extension-error.js';  /**   * Checks whether a given value is a non-array object. - * @param {*} value The value to check. + * @param {unknown} value The value to check.   * @returns {boolean} `true` if the value is an object and not an array, `false` otherwise.   */  export function isObject(value) { @@ -91,14 +48,20 @@ export function stringReverse(string) {  /**   * Creates a deep clone of an object or value. This is similar to `JSON.parse(JSON.stringify(value))`. - * @param {*} value The value to clone. - * @returns {*} A new clone of the value. + * @template T + * @param {T} value The value to clone. + * @returns {T} A new clone of the value.   * @throws An error if the value is circular and cannot be cloned.   */  export const clone = (() => { -    // eslint-disable-next-line no-shadow +    /** +     * @template T +     * @param {T} value +     * @returns {T} +     */ +    // eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow      function clone(value) { -        if (value === null) { return null; } +        if (value === null) { return /** @type {T} */ (null); }          switch (typeof value) {              case 'boolean':              case 'number': @@ -112,8 +75,15 @@ export const clone = (() => {          }      } +    /** +     * @template [T=unknown] +     * @param {T} value +     * @param {Set<unknown>} visited +     * @returns {T} +     * @throws {Error} +     */      function cloneInternal(value, visited) { -        if (value === null) { return null; } +        if (value === null) { return /** @type {T} */ (null); }          switch (typeof value) {              case 'boolean':              case 'number': @@ -122,13 +92,23 @@ export const clone = (() => {              case 'symbol':              case 'undefined':                  return value; -            case 'function': -                return cloneObject(value, visited);              case 'object': -                return Array.isArray(value) ? cloneArray(value, visited) : cloneObject(value, visited); +                return /** @type {T} */ ( +                    Array.isArray(value) ? +                    cloneArray(value, visited) : +                    cloneObject(/** @type {import('core').SerializableObject} */ (value), visited) +                ); +            default: +                throw new Error(`Cannot clone object of type ${typeof value}`);          }      } +    /** +     * @param {unknown[]} value +     * @param {Set<unknown>} visited +     * @returns {unknown[]} +     * @throws {Error} +     */      function cloneArray(value, visited) {          if (visited.has(value)) { throw new Error('Circular'); }          try { @@ -143,10 +123,17 @@ export const clone = (() => {          }      } +    /** +     * @param {import('core').SerializableObject} value +     * @param {Set<unknown>} visited +     * @returns {import('core').SerializableObject} +     * @throws {Error} +     */      function cloneObject(value, visited) {          if (visited.has(value)) { throw new Error('Circular'); }          try {              visited.add(value); +            /** @type {import('core').SerializableObject} */              const result = {};              for (const key in value) {                  if (Object.prototype.hasOwnProperty.call(value, key)) { @@ -164,12 +151,17 @@ export const clone = (() => {  /**   * Checks if an object or value is deeply equal to another object or value. - * @param {*} value1 The first value to check. - * @param {*} value2 The second value to check. + * @param {unknown} value1 The first value to check. + * @param {unknown} value2 The second value to check.   * @returns {boolean} `true` if the values are the same object, or deeply equal without cycles. `false` otherwise.   */  export const deepEqual = (() => { -    // eslint-disable-next-line no-shadow +    /** +     * @param {unknown} value1 +     * @param {unknown} value2 +     * @returns {boolean} +     */ +    // eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow      function deepEqual(value1, value2) {          if (value1 === value2) { return true; } @@ -185,6 +177,12 @@ export const deepEqual = (() => {          }      } +    /** +     * @param {unknown} value1 +     * @param {unknown} value2 +     * @param {Set<unknown>} visited1 +     * @returns {boolean} +     */      function deepEqualInternal(value1, value2, visited1) {          if (value1 === value2) { return true; } @@ -200,13 +198,23 @@ export const deepEqual = (() => {                  if (array !== Array.isArray(value2)) { return false; }                  if (visited1.has(value1)) { return false; }                  visited1.add(value1); -                return array ? areArraysEqual(value1, value2, visited1) : areObjectsEqual(value1, value2, visited1); +                return ( +                    array ? +                    areArraysEqual(/** @type {unknown[]} */ (value1), /** @type {unknown[]} */ (value2), visited1) : +                    areObjectsEqual(/** @type {import('core').UnknownObject} */ (value1), /** @type {import('core').UnknownObject} */ (value2), visited1) +                );              }              default:                  return false;          }      } +    /** +     * @param {import('core').UnknownObject} value1 +     * @param {import('core').UnknownObject} value2 +     * @param {Set<unknown>} visited1 +     * @returns {boolean} +     */      function areObjectsEqual(value1, value2, visited1) {          const keys1 = Object.keys(value1);          const keys2 = Object.keys(value2); @@ -220,6 +228,12 @@ export const deepEqual = (() => {          return true;      } +    /** +     * @param {unknown[]} value1 +     * @param {unknown[]} value2 +     * @param {Set<unknown>} visited1 +     * @returns {boolean} +     */      function areArraysEqual(value1, value2, visited1) {          const length = value1.length;          if (length !== value2.length) { return false; } @@ -251,76 +265,55 @@ export function generateId(length) {  /**   * Creates an unresolved promise that can be resolved later, outside the promise's executor function. - * @returns {{promise: Promise, resolve: Function, reject: Function}} An object `{promise, resolve, reject}`, containing the promise and the resolve/reject functions. + * @template T + * @returns {import('core').DeferredPromiseDetails<T>} An object `{promise, resolve, reject}`, containing the promise and the resolve/reject functions.   */  export function deferPromise() { +    /** @type {((value: T) => void)|undefined} */      let resolve; +    /** @type {((reason?: import('core').RejectionReason) => void)|undefined} */      let reject;      const promise = new Promise((resolve2, reject2) => {          resolve = resolve2;          reject = reject2;      }); -    return {promise, resolve, reject}; +    return { +        promise, +        resolve: /** @type {(value: T) => void} */ (resolve), +        reject: /** @type {(reason?: import('core').RejectionReason) => void} */ (reject) +    };  }  /**   * Creates a promise that is resolved after a set delay.   * @param {number} delay How many milliseconds until the promise should be resolved. If 0, the promise is immediately resolved. - * @param {*} [resolveValue] The value returned when the promise is resolved. - * @returns {Promise} A promise with two additional properties: `resolve` and `reject`, which can be used to complete the promise early. + * @returns {Promise<void>} A promise with two additional properties: `resolve` and `reject`, which can be used to complete the promise early.   */ -export function promiseTimeout(delay, resolveValue) { -    if (delay <= 0) { -        const promise = Promise.resolve(resolveValue); -        promise.resolve = () => {}; // NOP -        promise.reject = () => {}; // NOP -        return promise; -    } - -    let timer = null; -    let {promise, resolve, reject} = deferPromise(); - -    const complete = (callback, value) => { -        if (callback === null) { return; } -        if (timer !== null) { -            clearTimeout(timer); -            timer = null; -        } -        resolve = null; -        reject = null; -        callback(value); -    }; - -    const resolveWrapper = (value) => complete(resolve, value); -    const rejectWrapper = (value) => complete(reject, value); - -    timer = setTimeout(() => { -        timer = null; -        resolveWrapper(resolveValue); -    }, delay); - -    promise.resolve = resolveWrapper; -    promise.reject = rejectWrapper; - -    return promise; +export function promiseTimeout(delay) { +    return delay <= 0 ? Promise.resolve() : new Promise((resolve) => { setTimeout(resolve, delay); });  }  /**   * Creates a promise that will resolve after the next animation frame, using `requestAnimationFrame`.   * @param {number} [timeout] A maximum duration (in milliseconds) to wait until the promise resolves. If null or omitted, no timeout is used. - * @returns {Promise<{time: number, timeout: number}>} A promise that is resolved with `{time, timeout}`, where `time` is the timestamp from `requestAnimationFrame`, + * @returns {Promise<{time: number, timeout: boolean}>} A promise that is resolved with `{time, timeout}`, where `time` is the timestamp from `requestAnimationFrame`,   *   and `timeout` is a boolean indicating whether the cause was a timeout or not.   * @throws The promise throws an error if animation is not supported in this context, such as in a service worker.   */ -export function promiseAnimationFrame(timeout=null) { +export function promiseAnimationFrame(timeout) {      return new Promise((resolve, reject) => {          if (typeof cancelAnimationFrame !== 'function' || typeof requestAnimationFrame !== 'function') {              reject(new Error('Animation not supported in this context'));              return;          } +        /** @type {?import('core').Timeout} */          let timer = null; +        /** @type {?number} */          let frameRequest = null; +        /** +         * @param {number} time +         */          const onFrame = (time) => {              frameRequest = null;              if (timer !== null) { @@ -350,14 +343,12 @@ export function promiseAnimationFrame(timeout=null) {  /**   * Invokes a standard message handler. This function is used to react and respond   * to communication messages within the extension. - * @param {object} details Details about how to handle messages. - * @param {Function} details.handler A handler function which is passed `params` and `...extraArgs` as arguments. - * @param {boolean|string} details.async Whether or not the handler is async or not. Values include `false`, `true`, or `'dynamic'`. - *   When the value is `'dynamic'`, the handler should return an object of the format `{async: boolean, result: any}`. - * @param {object} params Information which was passed with the original message. - * @param {Function} callback A callback function which is invoked after the handler has completed. The value passed + * @template {import('core').SafeAny} TParams + * @param {import('core').MessageHandlerDetails} details Details about how to handle messages. + * @param {TParams} params Information which was passed with the original message. + * @param {(response: import('core').Response) => void} callback A callback function which is invoked after the handler has completed. The value passed   *   to the function is in the format: - *   - `{result: any}` if the handler invoked successfully. + *   - `{result: unknown}` if the handler invoked successfully.   *   - `{error: object}` if the handler thew an error. The error is serialized.   * @param {...*} extraArgs Additional arguments which are passed to the `handler` function.   * @returns {boolean} `true` if the function is invoked asynchronously, `false` otherwise. @@ -369,9 +360,9 @@ export function invokeMessageHandler({handler, async}, params, callback, ...extr              ({async, result: promiseOrResult} = promiseOrResult);          }          if (async) { -            promiseOrResult.then( +            /** @type {Promise<any>} */ (promiseOrResult).then(                  (result) => { callback({result}); }, -                (error) => { callback({error: serializeError(error)}); } +                (error) => { callback({error: ExtensionError.serialize(error)}); }              );              return true;          } else { @@ -379,12 +370,13 @@ export function invokeMessageHandler({handler, async}, params, callback, ...extr              return false;          }      } catch (error) { -        callback({error: serializeError(error)}); +        callback({error: ExtensionError.serialize(error)});          return false;      }  }  /** + * @template {string} TEventName   * Base class controls basic event dispatching.   */  export class EventDispatcher { @@ -392,13 +384,14 @@ export class EventDispatcher {       * Creates a new instance.       */      constructor() { +        /** @type {Map<string, ((details: import('core').SafeAny) => void)[]>} */          this._eventMap = new Map();      }      /**       * Triggers an event with the given name and specified argument. -     * @param {string} eventName The string representing the event's name. -     * @param {*} [details] The argument passed to the callback functions. +     * @param {TEventName} eventName The string representing the event's name. +     * @param {unknown} [details] The argument passed to the callback functions.       * @returns {boolean} `true` if any callbacks were registered, `false` otherwise.       */      trigger(eventName, details) { @@ -413,8 +406,8 @@ export class EventDispatcher {      /**       * Adds a single event listener to a specific event. -     * @param {string} eventName The string representing the event's name. -     * @param {Function} callback The event listener callback to add. +     * @param {TEventName} eventName The string representing the event's name. +     * @param {(details: import('core').SafeAny) => void} callback The event listener callback to add.       */      on(eventName, callback) {          let callbacks = this._eventMap.get(eventName); @@ -427,8 +420,8 @@ export class EventDispatcher {      /**       * Removes a single event listener from a specific event. -     * @param {string} eventName The string representing the event's name. -     * @param {Function} callback The event listener callback to add. +     * @param {TEventName} eventName The string representing the event's name. +     * @param {(details: import('core').SafeAny) => void} callback The event listener callback to add.       * @returns {boolean} `true` if the callback was removed, `false` otherwise.       */      off(eventName, callback) { @@ -450,7 +443,7 @@ export class EventDispatcher {      /**       * Checks if an event has any listeners. -     * @param {string} eventName The string representing the event's name. +     * @param {TEventName} eventName The string representing the event's name.       * @returns {boolean} `true` if the event has listeners, `false` otherwise.       */      hasListeners(eventName) { @@ -467,6 +460,7 @@ export class EventListenerCollection {       * Creates a new instance.       */      constructor() { +        /** @type {import('event-listener-collection').EventListenerDetails[]} */          this._eventListeners = [];      } @@ -479,50 +473,40 @@ export class EventListenerCollection {      }      /** -     * Adds an event listener of a generic type. -     * @param {string} type The type of event listener, which can be 'addEventListener', 'addListener', or 'on'. -     * @param {object} object The object to add the event listener to. -     * @param {...*} args The argument array passed to the object's event listener adding function. -     * @returns {void} -     * @throws An error if type is not an expected value. -     */ -    addGeneric(type, object, ...args) { -        switch (type) { -            case 'addEventListener': return this.addEventListener(object, ...args); -            case 'addListener': return this.addListener(object, ...args); -            case 'on': return this.on(object, ...args); -            default: throw new Error(`Invalid type: ${type}`); -        } -    } - -    /**       * Adds an event listener using `object.addEventListener`. The listener will later be removed using `object.removeEventListener`. -     * @param {object} object The object to add the event listener to. -     * @param {...*} args The argument array passed to the `addEventListener`/`removeEventListener` functions. +     * @param {import('event-listener-collection').EventTarget} target The object to add the event listener to. +     * @param {string} type The name of the event. +     * @param {EventListener | EventListenerObject | import('event-listener-collection').EventListenerFunction} listener The callback listener. +     * @param {AddEventListenerOptions | boolean} [options] Options for the event.       */ -    addEventListener(object, ...args) { -        object.addEventListener(...args); -        this._eventListeners.push(['removeEventListener', object, ...args]); +    addEventListener(target, type, listener, options) { +        target.addEventListener(type, listener, options); +        this._eventListeners.push({type: 'removeEventListener', target, eventName: type, listener, options});      }      /**       * Adds an event listener using `object.addListener`. The listener will later be removed using `object.removeListener`. -     * @param {object} object The object to add the event listener to. -     * @param {...*} args The argument array passed to the `addListener`/`removeListener` function. +     * @template {import('event-listener-collection').EventListenerFunction} TCallback +     * @template [TArgs=unknown] +     * @param {import('event-listener-collection').ExtensionEvent<TCallback, TArgs>} target The object to add the event listener to. +     * @param {TCallback} callback The callback. +     * @param {TArgs[]} args The extra argument array passed to the `addListener`/`removeListener` function.       */ -    addListener(object, ...args) { -        object.addListener(...args); -        this._eventListeners.push(['removeListener', object, ...args]); +    addListener(target, callback, ...args) { +        target.addListener(callback, ...args); +        this._eventListeners.push({type: 'removeListener', target, callback, args});      }      /**       * Adds an event listener using `object.on`. The listener will later be removed using `object.off`. -     * @param {object} object The object to add the event listener to. -     * @param {...*} args The argument array passed to the `on`/`off` function. +     * @template {string} TEventName +     * @param {EventDispatcher<TEventName>} target The object to add the event listener to. +     * @param {TEventName} eventName The string representing the event's name. +     * @param {(details: import('core').SafeAny) => void} callback The event listener callback to add.       */ -    on(object, ...args) { -        object.on(...args); -        this._eventListeners.push(['off', object, ...args]); +    on(target, eventName, callback) { +        target.on(eventName, callback); +        this._eventListeners.push({type: 'off', eventName, target, callback});      }      /** @@ -530,16 +514,16 @@ export class EventListenerCollection {       */      removeAllEventListeners() {          if (this._eventListeners.length === 0) { return; } -        for (const [removeFunctionName, object, ...args] of this._eventListeners) { -            switch (removeFunctionName) { +        for (const item of this._eventListeners) { +            switch (item.type) {                  case 'removeEventListener': -                    object.removeEventListener(...args); +                    item.target.removeEventListener(item.eventName, item.listener, item.options);                      break;                  case 'removeListener': -                    object.removeListener(...args); +                    item.target.removeListener(item.callback, ...item.args);                      break;                  case 'off': -                    object.off(...args); +                    item.target.off(item.eventName, item.callback);                      break;              }          } @@ -550,23 +534,28 @@ export class EventListenerCollection {  /**   * Class representing a generic value with an override stack.   * Changes can be observed by listening to the 'change' event. + * @template T + * @augments EventDispatcher<import('dynamic-property').EventType>   */  export class DynamicProperty extends EventDispatcher {      /**       * Creates a new instance with the specified value. -     * @param {*} value The value to assign. +     * @param {T} value The value to assign.       */      constructor(value) {          super(); +        /** @type {T} */          this._value = value; +        /** @type {T} */          this._defaultValue = value; +        /** @type {{value: T, priority: number, token: string}[]} */          this._overrides = [];      }      /**       * Gets the default value for the property, which is assigned to the       * public value property when no overrides are present. -     * @type {*} +     * @type {T}       */      get defaultValue() {          return this._defaultValue; @@ -576,7 +565,7 @@ export class DynamicProperty extends EventDispatcher {       * Assigns the default value for the property. If no overrides are present       * and if the value is different than the current default value,       * the 'change' event will be triggered. -     * @param {*} value The value to assign. +     * @param {T} value The value to assign.       */      set defaultValue(value) {          this._defaultValue = value; @@ -585,7 +574,7 @@ export class DynamicProperty extends EventDispatcher {      /**       * Gets the current value for the property, taking any overrides into account. -     * @type {*} +     * @type {T}       */      get value() {          return this._value; @@ -593,7 +582,7 @@ export class DynamicProperty extends EventDispatcher {      /**       * Gets the number of overrides added to the property. -     * @type {*} +     * @type {number}       */      get overrideCount() {          return this._overrides.length; @@ -606,9 +595,9 @@ export class DynamicProperty extends EventDispatcher {       * If the newly added override has the highest priority of all overrides       * and if the override value is different from the current value,       * the 'change' event will be fired. -     * @param {*} value The override value to assign. +     * @param {T} value The override value to assign.       * @param {number} [priority] The priority value to use, as a number. -     * @returns {string} A string token which can be passed to the clearOverride function +     * @returns {import('core').TokenString} A string token which can be passed to the clearOverride function       *   to remove the override.       */      setOverride(value, priority=0) { @@ -627,7 +616,7 @@ export class DynamicProperty extends EventDispatcher {       * Removes a specific override value. If the removed override       * had the highest priority, and the new value is different from       * the previous value, the 'change' event will be fired. -     * @param {string} token The token for the corresponding override which is to be removed. +     * @param {import('core').TokenString} token The token for the corresponding override which is to be removed.       * @returns {boolean} `true` if an override was returned, `false` otherwise.       */      clearOverride(token) { @@ -649,13 +638,14 @@ export class DynamicProperty extends EventDispatcher {          const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue;          if (this._value === value) { return; }          this._value = value; -        this.trigger('change', {value}); +        this.trigger('change', /** @type {import('dynamic-property').ChangeEventDetails<T>} */ ({value}));      }  }  /**   * This class handles logging of messages to the console and triggering   * an event for log calls. + * @augments EventDispatcher<import('log').LoggerEventType>   */  export class Logger extends EventDispatcher {      /** @@ -663,7 +653,8 @@ export class Logger extends EventDispatcher {       */      constructor() {          super(); -        this._extensionName = 'Yomichan'; +        /** @type {string} */ +        this._extensionName = 'Yomitan';          try {              const {name, version} = chrome.runtime.getManifest();              this._extensionName = `${name} ${version}`; @@ -674,13 +665,13 @@ export class Logger extends EventDispatcher {      /**       * Logs a generic error. This will trigger the 'log' event with the same arguments as the function invocation. -     * @param {Error|object|*} error The error to log. This is typically an `Error` or `Error`-like object. -     * @param {string} level The level to log at. Values include `'info'`, `'debug'`, `'warn'`, and `'error'`. +     * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. +     * @param {import('log').LogLevel} level The level to log at. Values include `'info'`, `'debug'`, `'warn'`, and `'error'`.       *   Other values will be logged at a non-error level. -     * @param {?object} [context] An optional context object for the error which should typically include a `url` field. +     * @param {?import('log').LogContext} [context] An optional context object for the error which should typically include a `url` field.       */      log(error, level, context=null) { -        if (!isObject(context)) { +        if (typeof context !== 'object' || context === null) {              context = {url: location.href};          } @@ -689,7 +680,11 @@ export class Logger extends EventDispatcher {              if (typeof error === 'string') {                  errorString = error;              } else { -                errorString = error.toString(); +                errorString = ( +                    typeof error === 'object' && error !== null ? +                    error.toString() : +                    `${error}` +                );                  if (/^\[object \w+\]$/.test(errorString)) {                      errorString = JSON.stringify(error);                  } @@ -700,14 +695,20 @@ export class Logger extends EventDispatcher {          let errorStack;          try { -            errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); +            errorStack = ( +                error instanceof Error ? +                (typeof error.stack === 'string' ? error.stack.trimEnd() : '') : +                '' +            );          } catch (e) {              errorStack = '';          }          let errorData;          try { -            errorData = error.data; +            if (error instanceof ExtensionError) { +                errorData = error.data; +            }          } catch (e) {              // NOP          } @@ -739,8 +740,8 @@ export class Logger extends EventDispatcher {      /**       * Logs a warning. This function invokes `log` internally. -     * @param {Error|object|*} error The error to log. This is typically an `Error` or `Error`-like object. -     * @param {?object} context An optional context object for the error which should typically include a `url` field. +     * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. +     * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field.       */      warn(error, context=null) {          this.log(error, 'warn', context); @@ -748,8 +749,8 @@ export class Logger extends EventDispatcher {      /**       * Logs an error. This function invokes `log` internally. -     * @param {Error|object|*} error The error to log. This is typically an `Error` or `Error`-like object. -     * @param {?object} context An optional context object for the error which should typically include a `url` field. +     * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. +     * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field.       */      error(error, context=null) {          this.log(error, 'error', context);  |