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) {  |