From 2eb824e54b3a3c9f88600838c0a2a02520a6c194 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:36:06 +0400 Subject: [PATCH 01/14] Renaming utils to TS --- src/lib/{utils.js => utils.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/{utils.js => utils.ts} (100%) diff --git a/src/lib/utils.js b/src/lib/utils.ts similarity index 100% rename from src/lib/utils.js rename to src/lib/utils.ts From 011139d19177a86f6f4b0f0dedd357cbefeba85c Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:40:25 +0400 Subject: [PATCH 02/14] Updating utils with type annotations from JSDoc --- src/lib/utils.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c7a6531..251fd43 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,13 +1,13 @@ /** * Traverse and find the object using the key path. - * @param {Object} targetObject Target object to traverse into. - * @param {string[]} path Path of keys to traverse deep into the object. - * @return {Object|null} Resulting object or null if nothing found (or target entry is not an object. + * @param targetObject Target object to traverse into. + * @param path Path of keys to traverse deep into the object. + * @return Resulting object or null if nothing found (or target entry is not an object). */ -export function findDeepObject(targetObject, path) { +export function findDeepObject(targetObject: Record, path: string[]): Object|null { let result = targetObject; - for (let key of path) { + for (const key of path) { if (!result || typeof result !== 'object') { return null; } @@ -27,17 +27,15 @@ export function findDeepObject(targetObject, path) { * * Gathered from right here: https://stackoverflow.com/a/3561711/16048617. Because I don't want to introduce some * library for that. - * - * @type {RegExp} */ -const unsafeRegExpCharacters = /[/\-\\^$*+?.()|[\]{}]/g; +const unsafeRegExpCharacters: RegExp = /[/\-\\^$*+?.()|[\]{}]/g; /** * Escape all the RegExp syntax-related characters in the following value. - * @param {string} value Original value. - * @return {string} Resulting value with all needed characters escaped. + * @param value Original value. + * @return Resulting value with all needed characters escaped. */ -export function escapeRegExp(value) { +export function escapeRegExp(value: string): string { unsafeRegExpCharacters.lastIndex = 0; return value.replace(unsafeRegExpCharacters, "\\$&"); } From d6487bbc2b3f6c13a7a35f2c8ebec6a0c3115268 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:41:00 +0400 Subject: [PATCH 03/14] Renaming tag categories to TS --- src/lib/booru/{tag-categories.js => tag-categories.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/booru/{tag-categories.js => tag-categories.ts} (100%) diff --git a/src/lib/booru/tag-categories.js b/src/lib/booru/tag-categories.ts similarity index 100% rename from src/lib/booru/tag-categories.js rename to src/lib/booru/tag-categories.ts From 01c08353f1f8d42d03591167fe1b81bd4f0dbb71 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:47:45 +0400 Subject: [PATCH 04/14] Renaming components & tags utils to TS --- src/lib/booru/scraped/parsing/PostParser.js | 2 +- src/lib/booru/{TagsUtils.js => tag-utils.ts} | 0 src/lib/components/ImageShowFullscreenButton.js | 2 +- src/lib/components/MaintenancePopup.js | 2 +- src/lib/components/MaintenanceStatusIcon.js | 2 +- src/lib/components/MediaBoxTools.js | 2 +- src/lib/components/MediaBoxWrapper.js | 4 ++-- src/lib/components/TagDropdownWrapper.js | 2 +- src/lib/components/TagsForm.js | 2 +- src/lib/components/base/BaseComponent.js | 2 +- .../components/base/{ComponentUtils.js => component-utils.ts} | 0 11 files changed, 10 insertions(+), 10 deletions(-) rename src/lib/booru/{TagsUtils.js => tag-utils.ts} (100%) rename src/lib/components/base/{ComponentUtils.js => component-utils.ts} (100%) diff --git a/src/lib/booru/scraped/parsing/PostParser.js b/src/lib/booru/scraped/parsing/PostParser.js index bac254d..af6d104 100644 --- a/src/lib/booru/scraped/parsing/PostParser.js +++ b/src/lib/booru/scraped/parsing/PostParser.js @@ -1,5 +1,5 @@ import PageParser from "$lib/booru/scraped/parsing/PageParser"; -import { buildTagsAndAliasesMap } from "$lib/booru/TagsUtils"; +import { buildTagsAndAliasesMap } from "$lib/booru/tag-utils"; export default class PostParser extends PageParser { /** @type {HTMLFormElement} */ diff --git a/src/lib/booru/TagsUtils.js b/src/lib/booru/tag-utils.ts similarity index 100% rename from src/lib/booru/TagsUtils.js rename to src/lib/booru/tag-utils.ts diff --git a/src/lib/components/ImageShowFullscreenButton.js b/src/lib/components/ImageShowFullscreenButton.js index 7c0b63a..fc00370 100644 --- a/src/lib/components/ImageShowFullscreenButton.js +++ b/src/lib/components/ImageShowFullscreenButton.js @@ -1,5 +1,5 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; import MiscSettings from "$lib/extension/settings/MiscSettings"; import { FullscreenViewer } from "$lib/components/FullscreenViewer"; diff --git a/src/lib/components/MaintenancePopup.js b/src/lib/components/MaintenancePopup.js index 6dc1f1c..3966be1 100644 --- a/src/lib/components/MaintenancePopup.js +++ b/src/lib/components/MaintenancePopup.js @@ -1,7 +1,7 @@ import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings"; import MaintenanceProfile from "$entities/MaintenanceProfile"; import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI"; import { tagsBlacklist } from "$config/tags"; import { emitterAt } from "$lib/components/events/comms"; diff --git a/src/lib/components/MaintenanceStatusIcon.js b/src/lib/components/MaintenanceStatusIcon.js index 4c0423d..78ebd57 100644 --- a/src/lib/components/MaintenanceStatusIcon.js +++ b/src/lib/components/MaintenanceStatusIcon.js @@ -1,5 +1,5 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; import { on } from "$lib/components/events/comms"; import { eventMaintenanceStateChanged } from "$lib/components/events/maintenance-popup-events"; diff --git a/src/lib/components/MediaBoxTools.js b/src/lib/components/MediaBoxTools.js index c7afac7..f2de6c5 100644 --- a/src/lib/components/MediaBoxTools.js +++ b/src/lib/components/MediaBoxTools.js @@ -1,5 +1,5 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; import { MaintenancePopup } from "$lib/components/MaintenancePopup"; import { on } from "$lib/components/events/comms"; import { eventActiveProfileChanged } from "$lib/components/events/maintenance-popup-events"; diff --git a/src/lib/components/MediaBoxWrapper.js b/src/lib/components/MediaBoxWrapper.js index 4559fd6..ff0ec34 100644 --- a/src/lib/components/MediaBoxWrapper.js +++ b/src/lib/components/MediaBoxWrapper.js @@ -1,6 +1,6 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; -import { buildTagsAndAliasesMap } from "$lib/booru/TagsUtils"; +import { getComponent } from "$lib/components/base/component-utils"; +import { buildTagsAndAliasesMap } from "$lib/booru/tag-utils"; import { on } from "$lib/components/events/comms"; import { eventTagsUpdated } from "$lib/components/events/maintenance-popup-events"; diff --git a/src/lib/components/TagDropdownWrapper.js b/src/lib/components/TagDropdownWrapper.js index 4c72dd1..3b09f08 100644 --- a/src/lib/components/TagDropdownWrapper.js +++ b/src/lib/components/TagDropdownWrapper.js @@ -1,7 +1,7 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; import MaintenanceProfile from "$entities/MaintenanceProfile"; import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; import CustomCategoriesResolver from "$lib/extension/CustomCategoriesResolver"; const isTagEditorProcessedKey = Symbol(); diff --git a/src/lib/components/TagsForm.js b/src/lib/components/TagsForm.js index 00cdfbc..5376ca3 100644 --- a/src/lib/components/TagsForm.js +++ b/src/lib/components/TagsForm.js @@ -1,5 +1,5 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; -import { getComponent } from "$lib/components/base/ComponentUtils"; +import { getComponent } from "$lib/components/base/component-utils"; export class TagsForm extends BaseComponent { /** diff --git a/src/lib/components/base/BaseComponent.js b/src/lib/components/base/BaseComponent.js index 3fd982e..04ac8fd 100644 --- a/src/lib/components/base/BaseComponent.js +++ b/src/lib/components/base/BaseComponent.js @@ -1,4 +1,4 @@ -import { bindComponent } from "$lib/components/base/ComponentUtils"; +import { bindComponent } from "$lib/components/base/component-utils"; /** * @abstract diff --git a/src/lib/components/base/ComponentUtils.js b/src/lib/components/base/component-utils.ts similarity index 100% rename from src/lib/components/base/ComponentUtils.js rename to src/lib/components/base/component-utils.ts From 92a0efaace75174f2e0fc155ef6525fca6bb50a3 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:49:28 +0400 Subject: [PATCH 05/14] Annotating tag utils with types --- src/lib/booru/tag-utils.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib/booru/tag-utils.ts b/src/lib/booru/tag-utils.ts index c430c4c..3270685 100644 --- a/src/lib/booru/tag-utils.ts +++ b/src/lib/booru/tag-utils.ts @@ -1,22 +1,21 @@ /** * Build the map containing both real tags and their aliases. * - * @param {string[]} realAndAliasedTags List combining aliases and tag names. - * @param {string[]} realTags List of actual tag names, excluding aliases. + * @param realAndAliasedTags List combining aliases and tag names. + * @param realTags List of actual tag names, excluding aliases. * - * @return {Map} Map where key is a tag or alias and value is an actual tag name. + * @return Map where key is a tag or alias and value is an actual tag name. */ -export function buildTagsAndAliasesMap(realAndAliasedTags, realTags) { - /** @type {Map} */ - const tagsAndAliasesMap = new Map(); +export function buildTagsAndAliasesMap(realAndAliasedTags: string[], realTags: string[]): Map { + const tagsAndAliasesMap: Map = new Map(); - for (let tagName of realTags) { + for (const tagName of realTags) { tagsAndAliasesMap.set(tagName, tagName); } - let realTagName = null; + let realTagName: string | null = null; - for (let tagNameOrAlias of realAndAliasedTags) { + for (const tagNameOrAlias of realAndAliasedTags) { if (tagsAndAliasesMap.has(tagNameOrAlias)) { realTagName = tagNameOrAlias; continue; From bda275677922e1475de35aef050bf41d8fd6b49f Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 6 Feb 2025 23:57:19 +0400 Subject: [PATCH 06/14] Annotating component utils with types --- src/lib/components/base/component-utils.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/components/base/component-utils.ts b/src/lib/components/base/component-utils.ts index 768621f..636b8f4 100644 --- a/src/lib/components/base/component-utils.ts +++ b/src/lib/components/base/component-utils.ts @@ -1,19 +1,26 @@ +import type { BaseComponent } from "$lib/components/base/BaseComponent"; + const instanceSymbol = Symbol('instance'); +interface ElementWithComponent extends HTMLElement { + [instanceSymbol]?: BaseComponent; +} + /** + * Get the component from the element, if there is one. * @param {HTMLElement} element - * @return {import('./BaseComponent').BaseComponent|null} + * @return */ -export function getComponent(element) { +export function getComponent(element: ElementWithComponent): BaseComponent | null { return element[instanceSymbol] || null; } /** * Bind the component to the selected element. - * @param {HTMLElement} element The element to bind the component to. - * @param {import('./BaseComponent').BaseComponent} instance The component instance. + * @param element The element to bind the component to. + * @param instance The component instance. */ -export function bindComponent(element, instance) { +export function bindComponent(element: ElementWithComponent, instance: BaseComponent): void { if (element[instanceSymbol]) { throw new Error('The element is already bound to a component.'); } From 08aa71c959f565382ad2d461fb50f776b82eba93 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 01:58:36 +0400 Subject: [PATCH 07/14] Renaming validators to TS --- src/lib/extension/transporting/{validators.js => validators.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/extension/transporting/{validators.js => validators.ts} (100%) diff --git a/src/lib/extension/transporting/validators.js b/src/lib/extension/transporting/validators.ts similarity index 100% rename from src/lib/extension/transporting/validators.js rename to src/lib/extension/transporting/validators.ts From d504ce3b043f55ae2b8ff0fb70e2e9efb61b012a Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 02:39:53 +0400 Subject: [PATCH 08/14] Converting the code to typescript, making validators more type-safe --- src/lib/extension/transporting/validators.ts | 59 ++++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/lib/extension/transporting/validators.ts b/src/lib/extension/transporting/validators.ts index 111c1be..dbb1223 100644 --- a/src/lib/extension/transporting/validators.ts +++ b/src/lib/extension/transporting/validators.ts @@ -1,9 +1,46 @@ +import type StorageEntity from "$lib/extension/base/StorageEntity"; + +/** + * Base information on the object which should be present on every entity. + */ +interface BaseImportableObject { + /** + * Numeric version of the entity for upgrading. + */ + v: number; + /** + * Unique ID of the entity. + */ + id: string; +} + +/** + * Utility type which combines base importable object and the entity type interfaces together. It strips away any types + * defined for the properties, since imported object can not be trusted and should be type-checked by the validators. + */ +type ImportableObject = { [ObjectKey in keyof BaseImportableObject]: any } + & { [SettingKey in keyof EntityType["settings"]]: any }; + +/** + * Function for validating the entities. + * @todo Probably would be better to replace the throw-catch method with some kind of result-error returning type. + * Errors are only properly definable in the JSDoc. + */ +type ValidationFunction = (importedObject: ImportableObject) => void; + +/** + * Mapping of validation functions for all entities present in the extension. Key is a name of entity and value is a + * function which throws an error when validation is failed. + */ +type EntitiesValidationMap = { + [EntityKey in keyof App.EntityNamesMap]?: ValidationFunction; +}; + /** * Map of validators for each entity. Function should throw the error if validation failed. - * @type {Map void)>} */ -const entitiesValidators = new Map([ - ['profiles', importedObject => { +const entitiesValidators: EntitiesValidationMap = { + profiles: importedObject => { if (importedObject.v !== 1) { throw new Error('Unsupported version!'); } @@ -18,22 +55,20 @@ const entitiesValidators = new Map([ ) { throw new Error('Invalid profile format detected!'); } - }] -]) + } +}; /** * Validate the structure of the entity. - * @param {Object} importedObject Object imported from JSON. - * @param {string} entityName Name of the entity to validate. Should be loaded from the entity class. + * @param importedObject Object imported from JSON. + * @param entityName Name of the entity to validate. Should be loaded from the entity class. * @throws {Error} Error in case validation failed with the reason stored in the message. */ -export function validateImportedEntity(importedObject, entityName) { - if (!entitiesValidators.has(entityName)) { +export function validateImportedEntity(importedObject: any, entityName: string) { + if (!entitiesValidators.hasOwnProperty(entityName)) { console.error(`Trying to validate entity without the validator present! Entity name: ${entityName}`); return; } - entitiesValidators - .get(entityName) - .call(null, importedObject); + entitiesValidators[entityName as keyof EntitiesValidationMap]!.call(null, importedObject); } From 392513f375c016d5aca3e40a7903e3ffc76f29ae Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 02:45:40 +0400 Subject: [PATCH 09/14] Renaming config controller to TS --- .../{ConfigurationController.js => ConfigurationController.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/extension/{ConfigurationController.js => ConfigurationController.ts} (100%) diff --git a/src/lib/extension/ConfigurationController.js b/src/lib/extension/ConfigurationController.ts similarity index 100% rename from src/lib/extension/ConfigurationController.js rename to src/lib/extension/ConfigurationController.ts From 9be8db85a2bf0cc63be6e3a253ac67366ad41a48 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 03:03:22 +0400 Subject: [PATCH 10/14] Converting config controller to TS --- src/lib/extension/ConfigurationController.ts | 28 +++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/lib/extension/ConfigurationController.ts b/src/lib/extension/ConfigurationController.ts index 7f973ff..218d553 100644 --- a/src/lib/extension/ConfigurationController.ts +++ b/src/lib/extension/ConfigurationController.ts @@ -1,25 +1,24 @@ import StorageHelper from "$lib/browser/StorageHelper"; export default class ConfigurationController { - /** @type {string} */ - #configurationName; + readonly #configurationName: string; /** * @param {string} configurationName Name of the configuration to work with. */ - constructor(configurationName) { + constructor(configurationName: string) { this.#configurationName = configurationName; } /** * Read the setting with the given name. * - * @param {string} settingName Setting name. - * @param {any} [defaultValue] Default value to return if the setting does not exist. Defaults to `null`. + * @param settingName Setting name. + * @param [defaultValue] Default value to return if the setting does not exist. Defaults to `null`. * - * @return {Promise} The setting value or the default value if the setting does not exist. + * @return The setting value or the default value if the setting does not exist. */ - async readSetting(settingName, defaultValue = null) { + async readSetting(settingName: string, defaultValue: DefaultType|null = null): Promise { const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); return settings[settingName] ?? defaultValue; } @@ -27,12 +26,12 @@ export default class ConfigurationController { /** * Write the given value to the setting. * - * @param {string} settingName Setting name. - * @param {any} value Value to write. + * @param settingName Setting name. + * @param value Value to write. * * @return {Promise} */ - async writeSetting(settingName, value) { + async writeSetting(settingName: string, value: any): Promise { const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); settings[settingName] = value; @@ -44,10 +43,8 @@ export default class ConfigurationController { * Delete the specific setting. * * @param {string} settingName Setting name to delete. - * - * @return {Promise} */ - async deleteSetting(settingName) { + async deleteSetting(settingName: string): Promise { const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); delete settings[settingName]; @@ -63,9 +60,8 @@ export default class ConfigurationController { * * @return {function(): void} Unsubscribe function. */ - subscribeToChanges(callback) { - /** @param {Record} changes */ - const changesSubscriber = changes => { + subscribeToChanges(callback: (record: Record) => void): () => void { + const changesSubscriber = (changes: Record) => { if (!changes[this.#configurationName]) { return; } From 7bb71807bc36ff5270e2dc5f405f1e1f957779d9 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 03:08:09 +0400 Subject: [PATCH 11/14] Renaming storage helper to TS --- src/lib/browser/{StorageHelper.js => StorageHelper.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/browser/{StorageHelper.js => StorageHelper.ts} (100%) diff --git a/src/lib/browser/StorageHelper.js b/src/lib/browser/StorageHelper.ts similarity index 100% rename from src/lib/browser/StorageHelper.js rename to src/lib/browser/StorageHelper.ts From c6d75e2b2afc3094bff070c11c044ef343d5b3a6 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 03:23:21 +0400 Subject: [PATCH 12/14] Converting storage helper to TS, adding types for subscribe functions --- src/lib/browser/StorageHelper.ts | 42 +++++++++----------- src/lib/extension/ConfigurationController.ts | 10 ++--- src/lib/extension/EntitiesController.ts | 8 ++-- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/lib/browser/StorageHelper.ts b/src/lib/browser/StorageHelper.ts index 1d4bf26..19c4afd 100644 --- a/src/lib/browser/StorageHelper.ts +++ b/src/lib/browser/StorageHelper.ts @@ -1,57 +1,53 @@ +/** + * Changes subscribe function. It receives changes with old and new value for keys of the storage. + */ +export type StorageChangeSubscriber = (changes: Record) => void; + /** * Helper class to read and write JSON objects to the local storage. - * @class */ -class StorageHelper { - /** - * @type {chrome.storage.StorageArea} - */ - #storageArea; +export default class StorageHelper { + readonly #storageArea: chrome.storage.StorageArea; - /** - * @param {chrome.storage.StorageArea} storageArea - */ - constructor(storageArea) { + constructor(storageArea: chrome.storage.StorageArea) { this.#storageArea = storageArea; } /** * Read the following entry from the local storage as a JSON object. * - * @param {string} key Key of the entry to read. - * @param {any} defaultValue Default value to return if the entry does not exist. + * @param key Key of the entry to read. + * @param defaultValue Default value to return if the entry does not exist. * - * @return {Promise} The JSON object or the default value if the entry does not exist. + * @return The JSON object or the default value if the entry does not exist. */ - async read(key, defaultValue = null) { + async read(key: string, defaultValue: DefaultType | null = null): Promise { return (await this.#storageArea.get(key))?.[key] || defaultValue; } /** * Write the following JSON object to the local storage. * - * @param {string} key Key of the entry to write. - * @param {any} value JSON object to write. + * @param key Key of the entry to write. + * @param value Value to write. */ - write(key, value) { + write(key: string, value: any): void { void this.#storageArea.set({[key]: value}); } /** * Subscribe to changes in the local storage. - * @param {function(Record): void} callback + * @param callback Listener function to receive changes. */ - subscribe(callback) { + subscribe(callback: StorageChangeSubscriber): void { this.#storageArea.onChanged.addListener(callback); } /** * Unsubscribe from changes in the local storage. - * @param {function(Record): void} callback + * @param callback Reference to the callback for unsubscribing. */ - unsubscribe(callback) { + unsubscribe(callback: StorageChangeSubscriber): void { this.#storageArea.onChanged.removeListener(callback); } } - -export default StorageHelper; diff --git a/src/lib/extension/ConfigurationController.ts b/src/lib/extension/ConfigurationController.ts index 218d553..860b852 100644 --- a/src/lib/extension/ConfigurationController.ts +++ b/src/lib/extension/ConfigurationController.ts @@ -1,4 +1,4 @@ -import StorageHelper from "$lib/browser/StorageHelper"; +import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/StorageHelper"; export default class ConfigurationController { readonly #configurationName: string; @@ -18,7 +18,7 @@ export default class ConfigurationController { * * @return The setting value or the default value if the setting does not exist. */ - async readSetting(settingName: string, defaultValue: DefaultType|null = null): Promise { + async readSetting(settingName: string, defaultValue: DefaultType | null = null): Promise { const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); return settings[settingName] ?? defaultValue; } @@ -61,7 +61,7 @@ export default class ConfigurationController { * @return {function(): void} Unsubscribe function. */ subscribeToChanges(callback: (record: Record) => void): () => void { - const changesSubscriber = (changes: Record) => { + const subscriber: StorageChangeSubscriber = changes => { if (!changes[this.#configurationName]) { return; } @@ -69,9 +69,9 @@ export default class ConfigurationController { callback(changes[this.#configurationName].newValue); } - ConfigurationController.#storageHelper.subscribe(changesSubscriber); + ConfigurationController.#storageHelper.subscribe(subscriber); - return () => ConfigurationController.#storageHelper.unsubscribe(changesSubscriber); + return () => ConfigurationController.#storageHelper.unsubscribe(subscriber); } static #storageHelper = new StorageHelper(chrome.storage.local); diff --git a/src/lib/extension/EntitiesController.ts b/src/lib/extension/EntitiesController.ts index da3f950..8a680e5 100644 --- a/src/lib/extension/EntitiesController.ts +++ b/src/lib/extension/EntitiesController.ts @@ -1,4 +1,4 @@ -import StorageHelper from "$lib/browser/StorageHelper"; +import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/StorageHelper"; import type StorageEntity from "$lib/extension/base/StorageEntity"; export default class EntitiesController { @@ -71,7 +71,7 @@ export default class EntitiesController { /** * Watch the changes made to the storage and call the callback when the entity changes. */ - const storageChangesSubscriber = (changes: Record) => { + const subscriber: StorageChangeSubscriber = changes => { if (!changes[entityName]) { return; } @@ -80,8 +80,8 @@ export default class EntitiesController { .then(callback); } - this.#storageHelper.subscribe(storageChangesSubscriber); + this.#storageHelper.subscribe(subscriber); - return () => this.#storageHelper.unsubscribe(storageChangesSubscriber); + return () => this.#storageHelper.unsubscribe(subscriber); } } From d9affcf5a0eab64aac330529d331f07885486f8f Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 03:47:45 +0400 Subject: [PATCH 13/14] Renaming query lexer to TS --- src/lib/booru/search/{QueryLexer.js => QueryLexer.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lib/booru/search/{QueryLexer.js => QueryLexer.ts} (100%) diff --git a/src/lib/booru/search/QueryLexer.js b/src/lib/booru/search/QueryLexer.ts similarity index 100% rename from src/lib/booru/search/QueryLexer.js rename to src/lib/booru/search/QueryLexer.ts From f5dd2f771170f877ab39d8a8df0f9730dad51ddd Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 7 Feb 2025 03:57:08 +0400 Subject: [PATCH 14/14] Adding type annotations to the query lexer --- src/lib/booru/search/QueryLexer.ts | 110 ++++++++++++----------------- 1 file changed, 46 insertions(+), 64 deletions(-) diff --git a/src/lib/booru/search/QueryLexer.ts b/src/lib/booru/search/QueryLexer.ts index 07e9c22..76d2d5f 100644 --- a/src/lib/booru/search/QueryLexer.ts +++ b/src/lib/booru/search/QueryLexer.ts @@ -1,8 +1,8 @@ export class Token { - index; - value; + readonly index: number; + readonly value: string; - constructor(index, value) { + constructor(index: number, value: string) { this.index = index; this.value = value; } @@ -28,12 +28,9 @@ export class BoostToken extends Token { } export class QuotedTermToken extends Token { - /** - * @type {string} - */ - #quotedValue; + readonly #quotedValue: string; - constructor(index, value, quotedValue) { + constructor(index: number, value: string, quotedValue: string) { super(index, value); this.#quotedValue = quotedValue; @@ -43,19 +40,11 @@ export class QuotedTermToken extends Token { return QuotedTermToken.decode(this.#quotedValue); } - /** - * @param {string} value - * @return {string} - */ - static decode(value) { + static decode(value: string): string { return value.replace(/\\([\\"])/g, "$1"); } - /** - * @param {string} value - * @return {string} - */ - static encode(value) { + static encode(value: string): string { return value.replace(/[\\"]/g, "\\$&"); } } @@ -63,6 +52,10 @@ export class QuotedTermToken extends Token { export class TermToken extends Token { } +type MatchResultCarry = { + match?: RegExpMatchArray | null +} + /** * Search query tokenizer. Should mostly work for the cases of parsing and finding the selected term for * auto-completion. Follows the rules described in the Philomena booru engine. @@ -70,38 +63,28 @@ export class TermToken extends Token { export class QueryLexer { /** * The original value to be parsed. - * @type {string} */ - #value; + readonly #value: string; /** * Current position of the parser in the value. - * @type {number} */ - #index = 0; + #index: number = 0; - /** - * @param {string} value - */ - constructor(value) { + constructor(value: string) { this.#value = value; } /** * Parse the query and get the list of tokens. * - * @return {Token[]} List of tokens. + * @return List of tokens. */ - parse() { - /** @type {Token[]} */ - const tokens = []; + parse(): Token[] { + const tokens: Token[] = []; + const result: MatchResultCarry = {}; - /** - * @type {{match: RegExpMatchArray|null}} - */ - const result = {}; - - let dirtyText; + let dirtyText: string; while (this.#index < this.#value.length) { if (this.#value[this.#index] === QueryLexer.#commaCharacter) { @@ -111,26 +94,26 @@ export class QueryLexer { } if (this.#match(QueryLexer.#negotiationOperator, result)) { - tokens.push(new NotToken(this.#index, result.match[0])); - this.#index += result.match[0].length; + tokens.push(new NotToken(this.#index, result.match![0])); + this.#index += result.match![0].length; continue; } if (this.#match(QueryLexer.#andOperator, result)) { - tokens.push(new AndToken(this.#index, result.match[0])); - this.#index += result.match[0].length; + tokens.push(new AndToken(this.#index, result.match![0])); + this.#index += result.match![0].length; continue; } if (this.#match(QueryLexer.#orOperator, result)) { - tokens.push(new OrToken(this.#index, result.match[0])); - this.#index += result.match[0].length; + tokens.push(new OrToken(this.#index, result.match![0])); + this.#index += result.match![0].length; continue; } if (this.#match(QueryLexer.#notOperator, result)) { - tokens.push(new NotToken(this.#index, result.match[0])); - this.#index += result.match[0].length; + tokens.push(new NotToken(this.#index, result.match![0])); + this.#index += result.match![0].length; continue; } @@ -147,19 +130,19 @@ export class QueryLexer { } if (this.#match(QueryLexer.#boostOperator, result)) { - tokens.push(new BoostToken(this.#index, result.match[0])); - this.#index += result.match[0].length; + tokens.push(new BoostToken(this.#index, result.match![0])); + this.#index += result.match![0].length; continue; } if (this.#match(QueryLexer.#whitespaces, result)) { - this.#index += result.match[0].length; + this.#index += result.match![0].length; continue; } if (this.#match(QueryLexer.#quotedText, result)) { - tokens.push(new QuotedTermToken(this.#index, result.match[0], result.match[1])); - this.#index += result.match[0].length; + tokens.push(new QuotedTermToken(this.#index, result.match![0], result.match![1])); + this.#index += result.match![0].length; continue; } @@ -180,25 +163,25 @@ export class QueryLexer { /** * Match the provided regular expression on the string with the current parser position. * - * @param {RegExp} targetRegExp Target RegExp to parse with. - * @param {{match: any}} [resultCarrier] Object for passing the results into. + * @param targetRegExp Target RegExp to parse with. + * @param [resultCarrier] Object for passing the results into. * - * @return {boolean} Is there a match? + * @return Is there a match? */ - #match(targetRegExp, resultCarrier = {}) { + #match(targetRegExp: RegExp, resultCarrier: MatchResultCarry = {}): boolean { return this.#matchAt(targetRegExp, this.#index, resultCarrier); } /** * Match the provided regular expression in the string with the specific index. * - * @param {RegExp} targetRegExp Target RegExp to parse with. - * @param {number} index Index to match the expression from. - * @param {{match: any}} [resultCarrier] Object for passing the results into. + * @param targetRegExp Target RegExp to parse with. + * @param index Index to match the expression from. + * @param [resultCarrier] Object for passing the results into. * - * @return {boolean} Is there a match? + * @return Is there a match? */ - #matchAt(targetRegExp, index, resultCarrier = {}) { + #matchAt(targetRegExp: RegExp, index: number, resultCarrier: MatchResultCarry = {}): boolean { targetRegExp.lastIndex = index; resultCarrier.match = this.#value.match(targetRegExp); @@ -212,11 +195,10 @@ export class QueryLexer { * * @return {string} Matched text. */ - #parseDirtyText(index) { - let resultValue = ''; + #parseDirtyText(index: number): string { + let resultValue: string = ''; - /** @type {{match: RegExpMatchArray|null}} */ - const result = {match: null}; + const result: MatchResultCarry = {match: null}; // Loop over while (index < this.#value.length) { @@ -226,8 +208,8 @@ export class QueryLexer { } if (this.#matchAt(QueryLexer.#dirtyTextContent, index, result)) { - resultValue += result.match[0]; - index += result.match[0].length; + resultValue += result.match![0]; + index += result.match![0].length; continue; }