summaryrefslogtreecommitdiff
path: root/ext/js/general
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/general')
-rw-r--r--ext/js/general/cache-map.js45
-rw-r--r--ext/js/general/object-property-accessor.js29
-rw-r--r--ext/js/general/regex-util.js8
-rw-r--r--ext/js/general/task-accumulator.js46
-rw-r--r--ext/js/general/text-source-map.js34
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) {