diff options
author | James Maa <jmaa@berkeley.edu> | 2024-05-09 15:42:35 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-09 07:42:35 +0000 |
commit | 13278a5cf67de69678d8c4c5fb97e6eb00c94c11 (patch) | |
tree | 1d77bcf97bb9c6f08c88c9f80ea0da735d5721c2 /ext/js | |
parent | 77fa1d0f64b66d6e4fe9c8795c7844206edbcaf2 (diff) |
Update eslint unsafe rule (#887)
* Enable @typescript-eslint/no-unsafe-assignment
* Updates
* Add missing import
* Updates
* Fix types?
* Fix tests
* Address comments
* Move TextProcessorVariant to types
* Update types/ext/translation-internal.d.ts
Co-authored-by: StefanVukovic99 <stefanvukovic44@gmail.com>
Signed-off-by: James Maa <jmaa@berkeley.edu>
---------
Signed-off-by: James Maa <jmaa@berkeley.edu>
Co-authored-by: toasted-nutbread <toasted-nutbread@users.noreply.github.com>
Co-authored-by: StefanVukovic99 <stefanvukovic44@gmail.com>
Diffstat (limited to 'ext/js')
-rw-r--r-- | ext/js/accessibility/google-docs-xray.js | 2 | ||||
-rw-r--r-- | ext/js/background/backend.js | 29 | ||||
-rw-r--r-- | ext/js/background/offscreen-proxy.js | 1 | ||||
-rw-r--r-- | ext/js/background/script-manager.js | 28 | ||||
-rw-r--r-- | ext/js/comm/anki-connect.js | 7 | ||||
-rw-r--r-- | ext/js/comm/frame-ancestry-handler.js | 1 | ||||
-rw-r--r-- | ext/js/comm/frame-client.js | 1 | ||||
-rw-r--r-- | ext/js/data/anki-note-builder.js | 1 | ||||
-rw-r--r-- | ext/js/data/database.js | 10 | ||||
-rw-r--r-- | ext/js/data/json-schema.js | 2 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 2 | ||||
-rw-r--r-- | ext/js/dictionary/dictionary-data-util.js | 1 | ||||
-rw-r--r-- | ext/js/display/display-history.js | 4 | ||||
-rw-r--r-- | ext/js/input/hotkey-help-controller.js | 6 | ||||
-rw-r--r-- | ext/js/language/translator.js | 19 | ||||
-rw-r--r-- | ext/js/pages/settings/backup-controller.js | 10 | ||||
-rw-r--r-- | ext/js/pages/settings/dictionary-import-controller.js | 1 | ||||
-rw-r--r-- | ext/js/templates/anki-template-renderer.js | 17 | ||||
-rw-r--r-- | ext/js/templates/template-renderer-media-provider.js | 2 | ||||
-rw-r--r-- | ext/js/templates/template-renderer.js | 2 |
20 files changed, 97 insertions, 49 deletions
diff --git a/ext/js/accessibility/google-docs-xray.js b/ext/js/accessibility/google-docs-xray.js index 15e1d50b..6723a468 100644 --- a/ext/js/accessibility/google-docs-xray.js +++ b/ext/js/accessibility/google-docs-xray.js @@ -17,7 +17,7 @@ /** Entry point. */ function main() { - /** @type {Window} */ + /** @type {unknown} */ // @ts-expect-error - Firefox Xray vision const window2 = window.wrappedJSObject; if (!(typeof window2 === 'object' && window2 !== null)) { return; } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 294c11db..d042a253 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1831,16 +1831,7 @@ export class Backend { } try { - const tabWindow = await new Promise((resolve, reject) => { - chrome.windows.get(tab.windowId, {}, (value) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(value); - } - }); - }); + const tabWindow = await this._getWindow(tab.windowId); if (!tabWindow.focused) { await /** @type {Promise<void>} */ (new Promise((resolve, reject) => { chrome.windows.update(tab.windowId, {focused: true}, () => { @@ -1859,6 +1850,23 @@ export class Backend { } /** + * @param {number} windowId + * @returns {Promise<chrome.windows.Window>} + */ + _getWindow(windowId) { + return new Promise((resolve, reject) => { + chrome.windows.get(windowId, {}, (value) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(value); + } + }); + }); + } + + /** * @param {number} tabId * @param {number} frameId * @param {?number} [timeout=null] @@ -2208,6 +2216,7 @@ export class Backend { async _injectAnkiNoteDictionaryMedia(ankiConnect, timestamp, dictionaryMediaDetails) { const targets = []; const detailsList = []; + /** @type {Map<string, {dictionary: string, path: string, media: ?import('dictionary-database').MediaDataStringContent}>} */ const detailsMap = new Map(); for (const {dictionary, path} of dictionaryMediaDetails) { const target = {dictionary, path}; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index e65ec65e..9e7b5b74 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -88,6 +88,7 @@ export class OffscreenProxy { if (!chrome.runtime.getContexts) { // Chrome version below 116 // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients // @ts-expect-error - Types not set up for service workers yet + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const matchedClients = await clients.matchAll(); // @ts-expect-error - Types not set up for service workers yet return await matchedClients.some((client) => client.url === offscreenUrl); diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js index 84213452..ea1702e9 100644 --- a/ext/js/background/script-manager.js +++ b/ext/js/background/script-manager.js @@ -60,16 +60,7 @@ export function injectStylesheet(type, content, tabId, frameId, allFrames) { * @returns {Promise<boolean>} `true` if a script is registered, `false` otherwise. */ export async function isContentScriptRegistered(id) { - const scripts = await new Promise((resolve, reject) => { - chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(result); - } - }); - }); + const scripts = await getRegisteredContentScripts([id]); for (const script of scripts) { if (script.id === id) { return true; @@ -155,3 +146,20 @@ function createContentScriptRegistrationOptions(details, id) { } return options; } + +/** + * @param {string[]} ids + * @returns {Promise<chrome.scripting.RegisteredContentScript[]>} + */ +function getRegisteredContentScripts(ids) { + return new Promise((resolve, reject) => { + chrome.scripting.getRegisteredContentScripts({ids}, (result) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(result); + } + }); + }); +} diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js index 6a008f40..23183e79 100644 --- a/ext/js/comm/anki-connect.js +++ b/ext/js/comm/anki-connect.js @@ -18,6 +18,7 @@ import {ExtensionError} from '../core/extension-error.js'; import {parseJson} from '../core/json.js'; +import {isObjectNotArray} from '../core/object-utilities.js'; import {getRootDeckName} from '../data/anki-util.js'; /** @@ -606,15 +607,15 @@ export class AnkiConnect { if (typeof modelName !== 'string') { throw this._createError(`Unexpected result type at index ${i}, field modelName: expected string, received ${this._getTypeName(modelName)}`, result); } - if (typeof fields !== 'object' || fields === null) { - throw this._createError(`Unexpected result type at index ${i}, field fields: expected string, received ${this._getTypeName(fields)}`, result); + if (!isObjectNotArray(fields)) { + throw this._createError(`Unexpected result type at index ${i}, field fields: expected object, received ${this._getTypeName(fields)}`, result); } const tags2 = /** @type {string[]} */ (this._normalizeArray(tags, -1, 'string', ', field tags')); const cards2 = /** @type {number[]} */ (this._normalizeArray(cards, -1, 'number', ', field cards')); /** @type {{[key: string]: import('anki').NoteFieldInfo}} */ const fields2 = {}; for (const [key, fieldInfo] of Object.entries(fields)) { - if (typeof fieldInfo !== 'object' || fieldInfo === null) { continue; } + if (!isObjectNotArray(fieldInfo)) { continue; } const {value, order} = fieldInfo; if (typeof value !== 'string' || typeof order !== 'number') { continue; } fields2[key] = {value, order}; diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 39288738..1915f121 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -288,6 +288,7 @@ export class FrameAncestryHandler { } /** @type {?ShadowRoot|undefined} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const shadowRoot = ( element.shadowRoot || // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index a30efa29..effd3e7c 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -83,6 +83,7 @@ export class FrameClient { */ _connectInternal(frame, targetOrigin, hostFrameId, setupFrame, timeout) { return new Promise((resolve, reject) => { + /** @type {Map<string, string>} */ const tokenMap = new Map(); /** @type {?import('core').Timeout} */ let timer = null; diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index 6a6a6177..aec8cdd9 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -88,6 +88,7 @@ export class AnkiNoteBuilder { } const formattedFieldValues = await Promise.all(formattedFieldValuePromises); + /** @type {Map<string, import('anki-note-builder').Requirement>} */ const uniqueRequirements = new Map(); /** @type {import('anki').NoteFields} */ const noteFields = {}; diff --git a/ext/js/data/database.js b/ext/js/data/database.js index 7f37347b..a53c8ddb 100644 --- a/ext/js/data/database.js +++ b/ext/js/data/database.js @@ -194,10 +194,10 @@ export class Database { request.onsuccess = (e) => { const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result; if (cursor) { - /** @type {TResult} */ + /** @type {unknown} */ const value = cursor.value; - if (noPredicate || predicate(value, predicateArg)) { - resolve(value, data); + if (noPredicate || predicate(/** @type {TResult} */ (value), predicateArg)) { + resolve(/** @type {TResult} */ (value), data); } else { cursor.continue(); } @@ -424,9 +424,9 @@ export class Database { request.onsuccess = (e) => { const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result; if (cursor) { - /** @type {TResult} */ + /** @type {unknown} */ const value = cursor.value; - results.push(value); + results.push(/** @type {TResult} */ (value)); cursor.continue(); } else { onSuccess(results, data); diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js index 9e1497e9..0a2b8d82 100644 --- a/ext/js/data/json-schema.js +++ b/ext/js/data/json-schema.js @@ -1263,7 +1263,7 @@ class JsonSchemaProxyHandler { /** * @param {import('ext/json-schema').ValueObjectOrArray} target * @param {string|number|symbol} property - * @param {import('core').SafeAny} value + * @param {unknown} value * @returns {boolean} * @throws {Error} */ diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index b6fb6686..ba404bc2 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -27,6 +27,7 @@ import {JsonSchema} from './json-schema.js'; // of the options object to a newer format. SafeAny is used for much of this, since every single // legacy format does not contain type definitions. /* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export class OptionsUtil { constructor() { @@ -1295,4 +1296,5 @@ export class OptionsUtil { } } +/* eslint-enable @typescript-eslint/no-unsafe-assignment */ /* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index a90668f4..dfdd5601 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -24,6 +24,7 @@ export function groupTermTags(dictionaryEntry) { const {headwords} = dictionaryEntry; const headwordCount = headwords.length; const uniqueCheck = (headwordCount > 1); + /** @type {Map<string, number>} */ const resultsIndexMap = new Map(); const results = []; for (let i = 0; i < headwordCount; ++i) { diff --git a/ext/js/display/display-history.js b/ext/js/display/display-history.js index 183368d0..0806e17b 100644 --- a/ext/js/display/display-history.js +++ b/ext/js/display/display-history.js @@ -37,6 +37,7 @@ export class DisplayHistory extends EventDispatcher { /** @type {Map<string, import('display-history').Entry>} */ this._historyMap = new Map(); + /** @type {unknown} */ const historyState = history.state; const {id, state} = ( isObjectNotArray(historyState) ? @@ -188,6 +189,7 @@ export class DisplayHistory extends EventDispatcher { /** */ _updateStateFromHistory() { + /** @type {unknown} */ let state = history.state; let id = null; if (isObjectNotArray(state)) { @@ -208,7 +210,7 @@ export class DisplayHistory extends EventDispatcher { // Fallback this._current.id = (typeof id === 'string' ? id : this._generateId()); - this._current.state = state; + this._current.state = /** @type {import('display-history').EntryState} */ (state); this._current.content = null; this._clear(); } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index b495365d..f85735fc 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -67,9 +67,10 @@ export class HotkeyHelpController { const hotkey = (global ? this._globalActionHotkeys : this._localActionHotkeys).get(action); for (let i = 0, ii = attributes.length; i < ii; ++i) { const attribute = attributes[i]; + /** @type {unknown} */ let value; if (typeof hotkey !== 'undefined') { - value = /** @type {unknown} */ (multipleValues ? values[i] : values); + value = multipleValues ? values[i] : values; if (typeof value === 'string') { value = value.replace(replacementPattern, hotkey); } @@ -158,7 +159,8 @@ export class HotkeyHelpController { if (typeof hotkey !== 'string') { return null; } const data = /** @type {unknown} */ (parseJson(hotkey)); if (!Array.isArray(data)) { return null; } - const [action, attributes, values] = /** @type {unknown[]} */ (data); + const dataArray = /** @type {unknown[]} */ (data); + const [action, attributes, values] = dataArray; if (typeof action !== 'string') { return null; } /** @type {string[]} */ const attributesArray = []; diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 845d53d5..0d26b2f0 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -289,6 +289,7 @@ export class Translator { return false; } + /** @type {Map<string, number>} */ const frequencyCounter = new Map(); for (const element of array1) { @@ -400,6 +401,7 @@ export class Translator { * @returns {Map<string, import('translation-internal').DatabaseDeinflection[]>} */ _groupDeinflectionsByTerm(deinflections) { + /** @type {Map<string, import('translation-internal').DatabaseDeinflection[]>} */ const result = new Map(); for (const deinflection of deinflections) { const {deinflectedText} = deinflection; @@ -455,7 +457,7 @@ export class Translator { /** @type {import('translation-internal').DatabaseDeinflection[]} */ const deinflections = []; const used = new Set(); - /** @type {Map<string, import('core').SafeAny>} */ + /** @type {import('translation-internal').TextCache} */ const sourceCache = new Map(); // For reusing text processors' outputs for ( @@ -498,14 +500,15 @@ export class Translator { /** * @param {import('language').TextProcessorWithId<unknown>[]} textProcessors - * @param {Map<string, unknown>} processorVariant + * @param {import('translation-internal').TextProcessorVariant} processorVariant * @param {string} text - * @param {Map<string, import('core').SafeAny>} textCache + * @param {import('translation-internal').TextCache} textCache * @returns {string} */ _applyTextProcessors(textProcessors, processorVariant, text, textCache) { for (const {id, textProcessor: {process}} of textProcessors) { const setting = processorVariant.get(id); + let level1 = textCache.get(text); if (!level1) { level1 = new Map(); @@ -522,7 +525,7 @@ export class Translator { text = process(text, setting); level2.set(setting, text); } else { - text = level2.get(setting); + text = level2.get(setting) || ''; } } @@ -681,6 +684,7 @@ export class Translator { /** @type {import('dictionary-database').TermExactRequest[]} */ const termList = []; const targetList = []; + /** @type {Map<string, {groups: import('translator').DictionaryEntryGroup[]}>} */ const targetMap = new Map(); for (const group of groupedDictionaryEntries) { @@ -1362,10 +1366,10 @@ export class Translator { /** * @param {Map<string, unknown[]>} arrayVariants - * @returns {Map<string, unknown>[]} + * @returns {import('translation-internal').TextProcessorVariant[]} */ _getArrayVariants(arrayVariants) { - /** @type {Map<string, unknown>[]} */ + /** @type {import('translation-internal').TextProcessorVariant[]} */ const results = []; const variantKeys = [...arrayVariants.keys()]; const entryVariantLengths = []; @@ -1376,7 +1380,7 @@ export class Translator { const totalVariants = entryVariantLengths.reduce((acc, length) => acc * length, 1); for (let variantIndex = 0; variantIndex < totalVariants; ++variantIndex) { - /** @type {Map<string, unknown>} */ + /** @type {import('translation-internal').TextProcessorVariant}} */ const variant = new Map(); let remainingIndex = variantIndex; @@ -2076,6 +2080,7 @@ export class Translator { * @param {boolean} ascending */ _updateSortFrequencies(dictionaryEntries, dictionary, ascending) { + /** @type {Map<number, number>} */ const frequencyMap = new Map(); for (const dictionaryEntry of dictionaryEntries) { const {definitions, frequencies} = dictionaryEntry; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 5c168849..f0876d3f 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -574,12 +574,16 @@ export class BackupController { * @returns {Promise<Blob>} */ async _exportDatabase(databaseName) { - const db = await new Dexie(databaseName).open(); + const DexieConstructor = /** @type {import('dexie').DexieConstructor} */ (/** @type {unknown} */ (Dexie)); + const db = new DexieConstructor(databaseName); + await db.open(); + /** @type {unknown} */ + // @ts-expect-error - The export function is declared as an extension which has no type information. const blob = await db.export({ progressCallback: this._databaseExportProgressCallback.bind(this) }); - await db.close(); - return blob; + db.close(); + return /** @type {Blob} */ (blob); } /** */ diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index f63eb49e..ecfadc1f 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -306,6 +306,7 @@ export class DictionaryImportController { * @param {Error[]} errors */ _showErrors(errors) { + /** @type {Map<string, number>} */ const uniqueErrors = new Map(); for (const error of errors) { log.error(error); diff --git a/ext/js/templates/anki-template-renderer.js b/ext/js/templates/anki-template-renderer.js index ae3e7a36..888be9b0 100644 --- a/ext/js/templates/anki-template-renderer.js +++ b/ext/js/templates/anki-template-renderer.js @@ -659,12 +659,19 @@ export class AnkiTemplateRenderer { } /** + * @param {import('template-renderer').HelperOptions} options + * @returns {import('anki-templates').NoteData} + */ + _getNoteDataFromOptions(options) { + return options.data.root; + } + + /** * @type {import('template-renderer').HelperFunction<string>} */ _formatGlossary(args, _context, options) { const [dictionary, content] = /** @type {[dictionary: string, content: import('dictionary-data').TermGlossaryContent]} */ (args); - /** @type {import('anki-templates').NoteData} */ - const data = options.data.root; + const data = this._getNoteDataFromOptions(options); if (typeof content === 'string') { return this._safeString(this._stringToMultiLineHtml(content)); } if (!(typeof content === 'object' && content !== null)) { return ''; } switch (content.type) { @@ -703,8 +710,7 @@ export class AnkiTemplateRenderer { * @type {import('template-renderer').HelperFunction<boolean>} */ _hasMedia(args, _context, options) { - /** @type {import('anki-templates').NoteData} */ - const data = options.data.root; + const data = this._getNoteDataFromOptions(options); return this._mediaProvider.hasMedia(data, args, options.hash); } @@ -712,8 +718,7 @@ export class AnkiTemplateRenderer { * @type {import('template-renderer').HelperFunction<?string>} */ _getMedia(args, _context, options) { - /** @type {import('anki-templates').NoteData} */ - const data = options.data.root; + const data = this._getNoteDataFromOptions(options); return this._mediaProvider.getMedia(data, args, options.hash); } diff --git a/ext/js/templates/template-renderer-media-provider.js b/ext/js/templates/template-renderer-media-provider.js index 2f238e20..c4b07369 100644 --- a/ext/js/templates/template-renderer-media-provider.js +++ b/ext/js/templates/template-renderer-media-provider.js @@ -81,6 +81,8 @@ export class TemplateRendererMediaProvider { let {value} = data; const {escape = true} = namedArgs; if (escape) { + // Handlebars is a custom version of the library without type information, so it's assumed to be "any". + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment value = Handlebars.Utils.escapeExpression(value); } return value; diff --git a/ext/js/templates/template-renderer.js b/ext/js/templates/template-renderer.js index 7bb93aa2..c5b7cd63 100644 --- a/ext/js/templates/template-renderer.js +++ b/ext/js/templates/template-renderer.js @@ -117,6 +117,8 @@ export class TemplateRenderer { let instance = cache.get(template); if (typeof instance === 'undefined') { this._updateCacheSize(this._cacheMaxSize - 1); + // Handlebars is a custom version of the library without type information, so it's assumed to be "any". + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment instance = /** @type {import('handlebars').TemplateDelegate<import('anki-templates').NoteData>} */ (Handlebars.compileAST(template)); cache.set(template, instance); } |