diff options
Diffstat (limited to 'ext/js/general')
-rw-r--r-- | ext/js/general/cache-map.js | 45 | ||||
-rw-r--r-- | ext/js/general/object-property-accessor.js | 29 | ||||
-rw-r--r-- | ext/js/general/regex-util.js | 8 | ||||
-rw-r--r-- | ext/js/general/task-accumulator.js | 46 | ||||
-rw-r--r-- | ext/js/general/text-source-map.js | 34 |
5 files changed, 128 insertions, 34 deletions
diff --git a/ext/js/general/cache-map.js b/ext/js/general/cache-map.js index 1ee385bf..cc706380 100644 --- a/ext/js/general/cache-map.js +++ b/ext/js/general/cache-map.js @@ -18,6 +18,7 @@ /** + * @template K,V * Class which caches a map of values, keeping the most recently accessed values. */ export class CacheMap { @@ -35,9 +36,13 @@ export class CacheMap { throw new Error('Invalid maxCount'); } + /** @type {number} */ this._maxSize = maxSize; + /** @type {Map<K, import('cache-map').Node<K, V>>} */ this._map = new Map(); + /** @type {import('cache-map').Node<K, V>} */ this._listFirst = this._createNode(null, null); + /** @type {import('cache-map').Node<K, V>} */ this._listLast = this._createNode(null, null); this._resetEndNodes(); } @@ -60,7 +65,7 @@ export class CacheMap { /** * Returns whether or not an element exists at the given key. - * @param {*} key The key of the element. + * @param {K} key The key of the element. * @returns {boolean} `true` if an element with the specified key exists, `false` otherwise. */ has(key) { @@ -69,20 +74,20 @@ export class CacheMap { /** * Gets an element at the given key, if it exists. Otherwise, returns undefined. - * @param {*} key The key of the element. - * @returns {*} The existing value at the key, if any; `undefined` otherwise. + * @param {K} key The key of the element. + * @returns {V|undefined} The existing value at the key, if any; `undefined` otherwise. */ get(key) { const node = this._map.get(key); if (typeof node === 'undefined') { return void 0; } this._updateRecency(node); - return node.value; + return /** @type {V} */ (node.value); } /** * Sets a value at a given key. - * @param {*} key The key of the element. - * @param {*} value The value to store in the cache. + * @param {K} key The key of the element. + * @param {V} value The value to store in the cache. */ set(key, value) { let node = this._map.get(key); @@ -98,9 +103,9 @@ export class CacheMap { // Remove for (let removeCount = this._map.size - this._maxSize; removeCount > 0; --removeCount) { - node = this._listLast.previous; + node = /** @type {import('cache-map').Node<K, V>} */ (this._listLast.previous); this._removeNode(node); - this._map.delete(node.key); + this._map.delete(/** @type {K} */ (node.key)); } } } @@ -115,28 +120,46 @@ export class CacheMap { // Private + /** + * @param {import('cache-map').Node<K, V>} node + */ _updateRecency(node) { this._removeNode(node); this._addNode(node, this._listFirst); } + /** + * @param {?K} key + * @param {?V} value + * @returns {import('cache-map').Node<K, V>} + */ _createNode(key, value) { return {key, value, previous: null, next: null}; } + /** + * @param {import('cache-map').Node<K, V>} node + * @param {import('cache-map').Node<K, V>} previous + */ _addNode(node, previous) { const next = previous.next; node.next = next; node.previous = previous; previous.next = node; - next.previous = node; + /** @type {import('cache-map').Node<K, V>} */ (next).previous = node; } + /** + * @param {import('cache-map').Node<K, V>} node + */ _removeNode(node) { - node.next.previous = node.previous; - node.previous.next = node.next; + /** @type {import('cache-map').Node<K, V>} */ (node.next).previous = node.previous; + /** @type {import('cache-map').Node<K, V>} */ (node.previous).next = node.next; } + /** + * @returns {void} + */ _resetEndNodes() { this._listFirst.next = this._listLast; this._listLast.previous = this._listFirst; diff --git a/ext/js/general/object-property-accessor.js b/ext/js/general/object-property-accessor.js index b8309ed2..d818c9d1 100644 --- a/ext/js/general/object-property-accessor.js +++ b/ext/js/general/object-property-accessor.js @@ -22,9 +22,10 @@ export class ObjectPropertyAccessor { /** * Create a new accessor for a specific object. - * @param {object} target The object which the getter and mutation methods are applied to. + * @param {unknown} target The object which the getter and mutation methods are applied to. */ constructor(target) { + /** @type {unknown} */ this._target = target; } @@ -33,7 +34,7 @@ export class ObjectPropertyAccessor { * @param {(string|number)[]} pathArray The path to the property on the target object. * @param {number} [pathLength] How many parts of the pathArray to use. * This parameter is optional and defaults to the length of pathArray. - * @returns {*} The value found at the path. + * @returns {unknown} The value found at the path. * @throws {Error} An error is thrown if pathArray is not valid for the target object. */ get(pathArray, pathLength) { @@ -44,7 +45,7 @@ export class ObjectPropertyAccessor { if (!ObjectPropertyAccessor.hasProperty(target, key)) { throw new Error(`Invalid path: ${ObjectPropertyAccessor.getPathString(pathArray.slice(0, i + 1))}`); } - target = target[key]; + target = /** @type {import('core').SerializableObject} */ (target)[key]; } return target; } @@ -52,7 +53,7 @@ export class ObjectPropertyAccessor { /** * Sets the value at the specified path. * @param {(string|number)[]} pathArray The path to the property on the target object. - * @param {*} value The value to assign to the property. + * @param {unknown} value The value to assign to the property. * @throws {Error} An error is thrown if pathArray is not valid for the target object. */ set(pathArray, value) { @@ -65,7 +66,7 @@ export class ObjectPropertyAccessor { throw new Error(`Invalid path: ${ObjectPropertyAccessor.getPathString(pathArray)}`); } - target[key] = value; + /** @type {import('core').SerializableObject} */ (target)[key] = value; } /** @@ -87,7 +88,7 @@ export class ObjectPropertyAccessor { throw new Error('Invalid type'); } - delete target[key]; + delete /** @type {import('core').SerializableObject} */ (target)[key]; } /** @@ -110,16 +111,16 @@ export class ObjectPropertyAccessor { const key2 = pathArray2[ii2]; if (!ObjectPropertyAccessor.isValidPropertyType(target2, key2)) { throw new Error(`Invalid path 2: ${ObjectPropertyAccessor.getPathString(pathArray2)}`); } - const value1 = target1[key1]; - const value2 = target2[key2]; + const value1 = /** @type {import('core').SerializableObject} */ (target1)[key1]; + const value2 = /** @type {import('core').SerializableObject} */ (target2)[key2]; - target1[key1] = value2; + /** @type {import('core').SerializableObject} */ (target1)[key1] = value2; try { - target2[key2] = value1; + /** @type {import('core').SerializableObject} */ (target2)[key2] = value1; } catch (e) { // Revert try { - target1[key1] = value1; + /** @type {import('core').SerializableObject} */ (target1)[key1] = value1; } catch (e2) { // NOP } @@ -178,7 +179,7 @@ export class ObjectPropertyAccessor { let value = ''; let escaped = false; for (const c of pathString) { - const v = c.codePointAt(0); + const v = /** @type {number} */ (c.codePointAt(0)); switch (state) { case 'empty': // Empty case 'id-start': // Expecting identifier start @@ -288,7 +289,7 @@ export class ObjectPropertyAccessor { /** * Checks whether an object or array has the specified property. - * @param {*} object The object to test. + * @param {unknown} object The object to test. * @param {string|number} property The property to check for existence. * This value should be a string if the object is a non-array object. * For arrays, it should be an integer. @@ -317,7 +318,7 @@ export class ObjectPropertyAccessor { /** * Checks whether a property is valid for the given object - * @param {object} object The object to test. + * @param {unknown} object The object to test. * @param {string|number} property The property to check for existence. * @returns {boolean} `true` if the property is correct for the given object type, otherwise `false`. * For arrays, this means that the property should be a positive integer. diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 298189d4..726ce9f2 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -61,7 +61,7 @@ export class RegexUtil { * Applies the replacement string for a given regular expression match. * @param {string} replacement The replacement string that follows the format of the standard * JavaScript regular expression replacement string. - * @param {object} match A match object returned from RegExp.match. + * @param {RegExpMatchArray} match A match object returned from RegExp.match. * @returns {string} A new string with the pattern replacement applied. */ static applyMatchReplacement(replacement, match) { @@ -79,11 +79,13 @@ export class RegexUtil { return groups[g2]; } } else { + let {index} = match; + if (typeof index !== 'number') { index = 0; } switch (g0) { case '$': return '$'; case '&': return match[0]; - case '`': return replacement.substring(0, match.index); - case '\'': return replacement.substring(match.index + g0.length); + case '`': return replacement.substring(0, index); + case '\'': return replacement.substring(index + g0.length); } } return g0; diff --git a/ext/js/general/task-accumulator.js b/ext/js/general/task-accumulator.js index cae58b94..cb136908 100644 --- a/ext/js/general/task-accumulator.js +++ b/ext/js/general/task-accumulator.js @@ -18,25 +18,46 @@ import {log} from '../core.js'; +/** + * @template K,V + */ export class TaskAccumulator { + /** + * @param {(tasks: [key: ?K, task: import('task-accumulator').Task<V>][]) => Promise<void>} runTasks + */ constructor(runTasks) { + /** @type {?Promise<void>} */ this._deferPromise = null; + /** @type {?Promise<void>} */ this._activePromise = null; + /** @type {import('task-accumulator').Task<V>[]} */ this._tasks = []; + /** @type {import('task-accumulator').Task<V>[]} */ this._tasksActive = []; + /** @type {Map<K, import('task-accumulator').Task<V>>} */ this._uniqueTasks = new Map(); + /** @type {Map<K, import('task-accumulator').Task<V>>} */ this._uniqueTasksActive = new Map(); + /** @type {() => Promise<void>} */ this._runTasksBind = this._runTasks.bind(this); + /** @type {() => void} */ this._tasksCompleteBind = this._tasksComplete.bind(this); - this._runTasks = runTasks; + /** @type {(tasks: [key: ?K, task: import('task-accumulator').Task<V>][]) => Promise<void>} */ + this._runTasksCallback = runTasks; } + /** + * @param {?K} key + * @param {V} data + * @returns {Promise<void>} + */ enqueue(key, data) { if (this._deferPromise === null) { const promise = this._activePromise !== null ? this._activePromise : Promise.resolve(); this._deferPromise = promise.then(this._runTasksBind); } + /** @type {import('task-accumulator').Task<V>} */ const task = {data, stale: false}; if (key !== null) { const activeTaskInfo = this._uniqueTasksActive.get(key); @@ -52,6 +73,9 @@ export class TaskAccumulator { return this._deferPromise; } + /** + * @returns {Promise<void>} + */ _runTasks() { this._deferPromise = null; @@ -64,18 +88,28 @@ export class TaskAccumulator { return this._activePromise; } + /** + * @returns {Promise<void>} + */ async _runTasksAsync() { try { - const allTasks = [ - ...this._tasksActive.map((taskInfo) => [null, taskInfo]), - ...this._uniqueTasksActive.entries() - ]; - await this._runTasks(allTasks); + /** @type {[key: ?K, task: import('task-accumulator').Task<V>][]} */ + const allTasks = []; + for (const taskInfo of this._tasksActive) { + allTasks.push([null, taskInfo]); + } + for (const [key, taskInfo] of this._uniqueTasksActive) { + allTasks.push([key, taskInfo]); + } + await this._runTasksCallback(allTasks); } catch (e) { log.error(e); } } + /** + * @returns {void} + */ _tasksComplete() { this._tasksActive.length = 0; this._uniqueTasksActive.clear(); diff --git a/ext/js/general/text-source-map.js b/ext/js/general/text-source-map.js index 6a136451..b03f6eb2 100644 --- a/ext/js/general/text-source-map.js +++ b/ext/js/general/text-source-map.js @@ -17,15 +17,26 @@ */ export class TextSourceMap { + /** + * @param {string} source + * @param {number[]|null} [mapping=null] + */ constructor(source, mapping=null) { + /** @type {string} */ this._source = source; + /** @type {?number[]} */ this._mapping = (mapping !== null ? TextSourceMap.normalizeMapping(mapping) : null); } + /** @type {string} */ get source() { return this._source; } + /** + * @param {unknown} other + * @returns {boolean} + */ equals(other) { if (this === other) { return true; @@ -61,6 +72,10 @@ export class TextSourceMap { return true; } + /** + * @param {number} finalLength + * @returns {number} + */ getSourceLength(finalLength) { const mapping = this._mapping; if (mapping === null) { @@ -74,6 +89,10 @@ export class TextSourceMap { return sourceLength; } + /** + * @param {number} index + * @param {number} count + */ combine(index, count) { if (count <= 0) { return; } @@ -89,6 +108,10 @@ export class TextSourceMap { this._mapping[index] = sum; } + /** + * @param {number} index + * @param {number[]} items + */ insert(index, ...items) { if (this._mapping === null) { this._mapping = TextSourceMap.createMapping(this._source); @@ -97,14 +120,25 @@ export class TextSourceMap { this._mapping.splice(index, 0, ...items); } + /** + * @returns {?number[]} + */ getMappingCopy() { return this._mapping !== null ? [...this._mapping] : null; } + /** + * @param {string} text + * @returns {number[]} + */ static createMapping(text) { return new Array(text.length).fill(1); } + /** + * @param {number[]} mapping + * @returns {number[]} + */ static normalizeMapping(mapping) { const result = []; for (const value of mapping) { |