cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => {
  while (end && (!end.countable || end.deleted)) {
    if (!end.deleted && end.content.constructor === ContentFormat) {
      updateCurrentAttributes(endAttributes,
      /** @type {ContentFormat} */
      end.content);
    }

    end = end.right;
  }

  let cleanups = 0;

  while (start !== end) {
    if (!start.deleted) {
      const content = start.content;

      switch (content.constructor) {
        case ContentFormat:
          {
            const {
              key,
              value
            } =
            /** @type {ContentFormat} */
            content;

            if ((endAttributes.get(key) || null) !== value || (startAttributes.get(key) || null) === value) {
              // Either this format is overwritten or it is not necessary because the attribute already existed.
              start.delete(transaction);
              cleanups++;
            }

            break;
          }
      }
    }

    start =
    /** @type {Item} */
    start.right;
  }

  return cleanups;
}
Call this function after string content has been deleted in order to
clean up formatting Items.

@param {Transaction} transaction
@param {Item} start
@param {Item|null} end exclusive end, automatically iterates to the next Content Item
@param {Map<string,any>} startAttributes
@param {Map<string,any>} endAttributes This attribute is modified!
@return {number} The amount of formatting Items deleted.

@function