diff options
Diffstat (limited to 'ext/js')
-rw-r--r-- | ext/js/background/backend.js | 8 | ||||
-rw-r--r-- | ext/js/data/database.js | 21 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 10 | ||||
-rw-r--r-- | ext/js/dictionary/dictionary-importer.js | 7 | ||||
-rw-r--r-- | ext/js/display/display-anki.js | 1 | ||||
-rw-r--r-- | ext/js/display/display-history.js | 10 | ||||
-rw-r--r-- | ext/js/display/display.js | 31 | ||||
-rw-r--r-- | ext/js/dom/text-source-generator.js | 4 | ||||
-rw-r--r-- | ext/js/general/regex-util.js | 15 | ||||
-rw-r--r-- | ext/js/input/hotkey-help-controller.js | 15 | ||||
-rw-r--r-- | ext/js/language/translator.js | 5 | ||||
-rw-r--r-- | ext/js/media/audio-downloader.js | 12 | ||||
-rw-r--r-- | ext/js/pages/settings/dictionary-controller.js | 1 | ||||
-rw-r--r-- | ext/js/templates/sandbox/anki-template-renderer.js | 42 |
14 files changed, 147 insertions, 35 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 03c0b5fe..79023ac9 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -349,12 +349,18 @@ export class Backend { _onWebExtensionEventWrapper(handler) { return /** @type {T} */ ((...args) => { if (this._isPrepared) { + // This is using SafeAny to just forward the parameters + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument handler(...args); return; } this._prepareCompletePromise.then( - () => { handler(...args); }, + () => { + // This is using SafeAny to just forward the parameters + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + handler(...args); + }, () => {} // NOP ); }); diff --git a/ext/js/data/database.js b/ext/js/data/database.js index e8575be2..7f37347b 100644 --- a/ext/js/data/database.js +++ b/ext/js/data/database.js @@ -194,7 +194,8 @@ export class Database { request.onsuccess = (e) => { const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result; if (cursor) { - const {value} = cursor; + /** @type {TResult} */ + const value = cursor.value; if (noPredicate || predicate(value, predicateArg)) { resolve(value, data); } else { @@ -353,7 +354,9 @@ export class Database { for (const {version, stores} of upgrades) { if (oldVersion >= version) { continue; } - for (const [objectStoreName, {primaryKey, indices}] of Object.entries(stores)) { + /** @type {[objectStoreName: string, value: import('database').StoreDefinition][]} */ + const entries = Object.entries(stores); + for (const [objectStoreName, {primaryKey, indices}] of entries) { const existingObjectStoreNames = transaction.objectStoreNames || db.objectStoreNames; const objectStore = ( this._listContains(existingObjectStoreNames, objectStoreName) ? @@ -394,8 +397,14 @@ export class Database { */ _getAllFast(objectStoreOrIndex, query, onSuccess, onReject, data) { const request = objectStoreOrIndex.getAll(query); - request.onerror = (e) => onReject(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).error, data); - request.onsuccess = (e) => onSuccess(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).result, data); + request.onerror = (e) => { + const target = /** @type {IDBRequest<TResult[]>} */ (e.target); + onReject(target.error, data); + }; + request.onsuccess = (e) => { + const target = /** @type {IDBRequest<TResult[]>} */ (e.target); + onSuccess(target.result, data); + }; } /** @@ -415,7 +424,9 @@ export class Database { request.onsuccess = (e) => { const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result; if (cursor) { - results.push(cursor.value); + /** @type {TResult} */ + const value = cursor.value; + results.push(value); cursor.continue(); } else { onSuccess(results, data); diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 8ef52972..de30f52a 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -22,6 +22,11 @@ import {escapeRegExp, isObject} from '../core/utilities.js'; import {TemplatePatcher} from '../templates/template-patcher.js'; import {JsonSchema} from './json-schema.js'; +// Some type safety rules are disabled for this file since it deals with upgrading an older format +// 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 */ + export class OptionsUtil { constructor() { /** @type {?TemplatePatcher} */ @@ -119,6 +124,9 @@ export class OptionsUtil { } }); }); + if (typeof optionsStr !== 'string') { + throw new Error('Invalid value for options'); + } options = parseJson(optionsStr); } catch (e) { // NOP @@ -1197,3 +1205,5 @@ export class OptionsUtil { }); } } + +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/ext/js/dictionary/dictionary-importer.js b/ext/js/dictionary/dictionary-importer.js index fbfe40d0..16998a8f 100644 --- a/ext/js/dictionary/dictionary-importer.js +++ b/ext/js/dictionary/dictionary-importer.js @@ -734,7 +734,8 @@ export class DictionaryImporter { const results = []; for (const file of files) { const content = await this._getData(file, new TextWriter()); - const entries = /** @type {unknown} */ (parseJson(content)); + /** @type {unknown} */ + const entries = parseJson(content); startIndex = progressData.index; this._progress(); @@ -748,8 +749,8 @@ export class DictionaryImporter { this._progress(); if (Array.isArray(entries)) { - for (const entry of entries) { - results.push(convertEntry(/** @type {TEntry} */ (entry), dictionaryTitle)); + for (const entry of /** @type {TEntry[]} */ (entries)) { + results.push(convertEntry(entry, dictionaryTitle)); } } } diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index fb7bd85f..9a6b96c7 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -376,6 +376,7 @@ export class DisplayAnki { _updateAdderButtons(dictionaryEntryDetails) { const displayTags = this._displayTags; for (let i = 0, ii = dictionaryEntryDetails.length; i < ii; ++i) { + /** @type {?Set<number>} */ let allNoteIds = null; for (const {mode, canAdd, noteIds, noteInfos, ankiError} of dictionaryEntryDetails[i].modeMap.values()) { const button = this._adderButtonFind(i, mode); diff --git a/ext/js/display/display-history.js b/ext/js/display/display-history.js index 67690219..bc4f1539 100644 --- a/ext/js/display/display-history.js +++ b/ext/js/display/display-history.js @@ -37,9 +37,15 @@ export class DisplayHistory extends EventDispatcher { this._historyMap = new Map(); const historyState = history.state; - const {id, state} = isObject(historyState) ? historyState : {id: null, state: null}; + const {id, state} = ( + typeof historyState === 'object' && historyState !== null ? + historyState : + {id: null, state: null} + ); + /** @type {?import('display-history').EntryState} */ + const stateObject = typeof state === 'object' || state === null ? state : null; /** @type {import('display-history').Entry} */ - this._current = this._createHistoryEntry(id, location.href, state, null, null); + this._current = this._createHistoryEntry(id, location.href, stateObject, null, null); } /** @type {?import('display-history').EntryState} */ diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 750e0d69..f6efb5ac 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1934,20 +1934,28 @@ export class Display extends EventDispatcher { this._hotkeyHandler.setHotkeys(this._pageType, options.inputs.hotkeys); } - /** */ - async _closeTab() { - const tab = await new Promise((resolve, reject) => { + /** + * @returns {Promise<?chrome.tabs.Tab>} + */ + _getCurrentTab() { + return new Promise((resolve, reject) => { chrome.tabs.getCurrent((result) => { const e = chrome.runtime.lastError; if (e) { reject(new Error(e.message)); } else { - resolve(result); + resolve(typeof result !== 'undefined' ? result : null); } }); }); - const tabId = tab.id; - await /** @type {Promise<void>} */ (new Promise((resolve, reject) => { + } + + /** + * @param {number} tabId + * @returns {Promise<void>} + */ + _removeTab(tabId) { + return new Promise((resolve, reject) => { chrome.tabs.remove(tabId, () => { const e = chrome.runtime.lastError; if (e) { @@ -1956,7 +1964,16 @@ export class Display extends EventDispatcher { resolve(); } }); - })); + }); + } + + /** */ + async _closeTab() { + const tab = await this._getCurrentTab(); + if (tab === null) { return; } + const tabId = tab.id; + if (typeof tabId === 'undefined') { return; } + await this._removeTab(tabId); } /** */ diff --git a/ext/js/dom/text-source-generator.js b/ext/js/dom/text-source-generator.js index 68bf036a..c4711411 100644 --- a/ext/js/dom/text-source-generator.js +++ b/ext/js/dom/text-source-generator.js @@ -462,6 +462,7 @@ export class TextSourceGenerator { * @returns {?Range} */ _caretPositionFromPointNormalizeStyles(x, y, nextElement) { + /** @type {Map<Element, ?string>} */ const previousStyles = new Map(); try { while (true) { @@ -490,7 +491,7 @@ export class TextSourceGenerator { // Elements with user-select: all will return the element // instead of a text point inside the element. if (this._isElementUserSelectAll(/** @type {Element} */ (node))) { - if (previousStyles.has(node)) { + if (previousStyles.has(/** @type {Element} */ (node))) { // Recursive return null; } @@ -524,6 +525,7 @@ export class TextSourceGenerator { * @returns {?Range} */ _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) { + /** @type {?Map<Element, ?string>} */ let previousStyles = null; try { let i = 0; diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index f6eca3b6..e0982154 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -46,7 +46,9 @@ export function applyTextReplacement(text, sourceMap, pattern, replacement) { pattern.lastIndex += delta; if (actualReplacementLength > 0) { - sourceMap.insert(index, ...(new Array(actualReplacementLength).fill(0))); + /** @type {number[]} */ + const zeroes = new Array(actualReplacementLength).fill(0); + sourceMap.insert(index, ...zeroes); sourceMap.combine(index - 1 + actualReplacementLength, matchText.length); } else { sourceMap.combine(index, matchText.length); @@ -65,7 +67,13 @@ export function applyTextReplacement(text, sourceMap, pattern, replacement) { export function applyMatchReplacement(replacement, match) { const pattern = matchReplacementPattern; pattern.lastIndex = 0; - return replacement.replace(pattern, (g0, g1, g2) => { + /** + * @param {string} g0 + * @param {string} g1 + * @param {string} g2 + * @returns {string} + */ + const replacer = (g0, g1, g2) => { if (typeof g1 !== 'undefined') { const matchIndex = Number.parseInt(g1, 10); if (matchIndex >= 1 && matchIndex <= match.length) { @@ -87,5 +95,6 @@ export function applyMatchReplacement(replacement, match) { } } return g0; - }); + }; + return replacement.replace(pattern, replacer); } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index dbb430dc..1fa9372f 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -89,10 +89,10 @@ export class HotkeyHelpController { // Private /** - * @param {Map<string, string>} commandMap + * @returns {Promise<chrome.commands.Command[]>} */ - async _setupGlobalCommands(commandMap) { - const commands = await new Promise((resolve, reject) => { + _getAllCommands() { + return new Promise((resolve, reject) => { if (!(isObject(chrome.commands) && typeof chrome.commands.getAll === 'function')) { resolve([]); return; @@ -107,10 +107,17 @@ export class HotkeyHelpController { } }); }); + } + + /** + * @param {Map<string, string>} commandMap + */ + async _setupGlobalCommands(commandMap) { + const commands = await this._getAllCommands(); commandMap.clear(); for (const {name, shortcut} of commands) { - if (shortcut.length === 0) { continue; } + if (typeof name !== 'string' || typeof shortcut !== 'string' || shortcut.length === 0) { continue; } const {key, modifiers} = this._hotkeyUtil.convertCommandToInput(shortcut); commandMap.set(name, this._hotkeyUtil.getInputDisplayValue(key, modifiers)); } diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 568c12bd..b10af226 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -137,6 +137,7 @@ export class Translator { text = this._getJapaneseOnlyText(text); } const {enabledDictionaryMap} = options; + /** @type {Set<string>} */ const kanjiUnique = new Set(); for (const c of text) { kanjiUnique.add(c); @@ -702,6 +703,7 @@ export class Translator { * @returns {import('dictionary').TermDictionaryEntry[]} */ _groupDictionaryEntriesByHeadword(dictionaryEntries, tagAggregator) { + /** @type {Map<string, import('dictionary').TermDictionaryEntry[]>} */ const groups = new Map(); for (const dictionaryEntry of dictionaryEntries) { const {inflectionRuleChainCandidates, headwords: [{term, reading}]} = dictionaryEntry; @@ -1039,8 +1041,11 @@ export class Translator { * @param {TranslatorTagAggregator} tagAggregator */ async _addTermMeta(dictionaryEntries, enabledDictionaryMap, tagAggregator) { + /** @type {Map<string, Map<string, {headwordIndex: number, pronunciations: import('dictionary').TermPronunciation[], frequencies: import('dictionary').TermFrequency[]}[]>>} */ const headwordMap = new Map(); + /** @type {string[]} */ const headwordMapKeys = []; + /** @type {Map<string, {headwordIndex: number, pronunciations: import('dictionary').TermPronunciation[], frequencies: import('dictionary').TermFrequency[]}[]>[]} */ const headwordReadingMaps = []; for (const {headwords, pronunciations, frequencies} of dictionaryEntries) { diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index d3bec75a..4cb3eb81 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -304,7 +304,17 @@ export class AudioDownloader { throw new Error('No custom URL defined'); } const data = {term, reading}; - return url.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[/** @type {'term'|'reading'} */ (m1)]}` : m0)); + /** + * @param {string} m0 + * @param {string} m1 + * @returns {string} + */ + const replacer = (m0, m1) => ( + Object.prototype.hasOwnProperty.call(data, m1) ? + `${data[/** @type {'term'|'reading'} */ (m1)]}` : + m0 + ); + return url.replace(/\{([^}]*)\}/g, replacer); } /** diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index a6760268..a0e0c196 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -548,6 +548,7 @@ export class DictionaryController { optionsFull = await settingsController.getOptionsFull(); } + /** @type {Set<string>} */ const installedDictionaries = new Set(); for (const {title} of dictionaries) { installedDictionaries.add(title); diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index ad2b0042..d2bb25d5 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -674,6 +674,7 @@ export class AnkiTemplateRenderer { */ _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; if (typeof content === 'string') { return this._stringToMultiLineHtml(this._escape(content)); } if (!(typeof content === 'object' && content !== null)) { return ''; } @@ -713,31 +714,42 @@ export class AnkiTemplateRenderer { * @type {import('template-renderer').HelperFunction<boolean>} */ _hasMedia(args, _context, options) { - return this._mediaProvider.hasMedia(options.data.root, args, options.hash); + /** @type {import('anki-templates').NoteData} */ + const data = options.data.root; + return this._mediaProvider.hasMedia(data, args, options.hash); } /** * @type {import('template-renderer').HelperFunction<?string>} */ _getMedia(args, _context, options) { - return this._mediaProvider.getMedia(options.data.root, args, options.hash); + /** @type {import('anki-templates').NoteData} */ + const data = options.data.root; + return this._mediaProvider.getMedia(data, args, options.hash); } /** * @type {import('template-renderer').HelperFunction<string>} */ _pronunciation(_args, _context, options) { - let {format, reading, downstepPosition, nasalPositions, devoicePositions} = options.hash; - - if (typeof reading !== 'string' || reading.length === 0) { return ''; } - if (typeof downstepPosition !== 'number') { return ''; } - if (!Array.isArray(nasalPositions)) { nasalPositions = []; } - if (!Array.isArray(devoicePositions)) { devoicePositions = []; } + const {format, reading, downstepPosition} = options.hash; + + if ( + typeof reading !== 'string' || + reading.length === 0 || + typeof downstepPosition !== 'number' + ) { + return ''; + } const morae = getKanaMorae(reading); switch (format) { case 'text': + { + const nasalPositions = this._getValidNumberArray(options.hash.nasalPositions); + const devoicePositions = this._getValidNumberArray(options.hash.devoicePositions); return this._getPronunciationHtml(createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions)); + } case 'graph': return this._getPronunciationHtml(createPronunciationGraph(morae, downstepPosition)); case 'position': @@ -748,6 +760,20 @@ export class AnkiTemplateRenderer { } /** + * @param {unknown} value + * @returns {number[]} + */ + _getValidNumberArray(value) { + const result = []; + if (Array.isArray(value)) { + for (const item of value) { + if (typeof item === 'number') { result.push(item); } + } + } + return result; + } + + /** * @type {import('template-renderer').HelperFunction<string>} */ _hiragana(args, context, options) { |