aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
authorJames Maa <jmaa@berkeley.edu>2024-05-09 15:42:35 +0800
committerGitHub <noreply@github.com>2024-05-09 07:42:35 +0000
commit13278a5cf67de69678d8c4c5fb97e6eb00c94c11 (patch)
tree1d77bcf97bb9c6f08c88c9f80ea0da735d5721c2 /ext/js
parent77fa1d0f64b66d6e4fe9c8795c7844206edbcaf2 (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.js2
-rw-r--r--ext/js/background/backend.js29
-rw-r--r--ext/js/background/offscreen-proxy.js1
-rw-r--r--ext/js/background/script-manager.js28
-rw-r--r--ext/js/comm/anki-connect.js7
-rw-r--r--ext/js/comm/frame-ancestry-handler.js1
-rw-r--r--ext/js/comm/frame-client.js1
-rw-r--r--ext/js/data/anki-note-builder.js1
-rw-r--r--ext/js/data/database.js10
-rw-r--r--ext/js/data/json-schema.js2
-rw-r--r--ext/js/data/options-util.js2
-rw-r--r--ext/js/dictionary/dictionary-data-util.js1
-rw-r--r--ext/js/display/display-history.js4
-rw-r--r--ext/js/input/hotkey-help-controller.js6
-rw-r--r--ext/js/language/translator.js19
-rw-r--r--ext/js/pages/settings/backup-controller.js10
-rw-r--r--ext/js/pages/settings/dictionary-import-controller.js1
-rw-r--r--ext/js/templates/anki-template-renderer.js17
-rw-r--r--ext/js/templates/template-renderer-media-provider.js2
-rw-r--r--ext/js/templates/template-renderer.js2
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);
}