features/removeTags.js

// @ts-check
/**
 * This class removes tag lines after a  `jsdoc-remove-next-tag` tag is found.
 */
class RemoveTags {
  /**
   * @param {EventEmitter} events       To hook to the necessary events to parse the code.
   * @param {EventNames}   EVENT_NAMES  To get the name of the events the class needs to
   *                                    listen for.
   */
  constructor(events, EVENT_NAMES) {
    /**
     * The list of comments that use `jsdoc-remove`.
     *
     * @type {string[]}
     * @access protected
     * @ignore
     */
    this._comments = [];
    /**
     * The expression that validates that the `jsdoc-remove-next-tag` tag is being used.
     * This expression is used on multiple places, that's why it's declared as a property.
     *
     * @type {RegExp}
     * @access protected
     * @ignore
     */
    this._removeExpression = /\s*\* @jsdoc-remove-next-tag\s*/;
    // Setup the listeners.
    events.on(EVENT_NAMES.newComment, this._readComment.bind(this));
    events.on(EVENT_NAMES.commentsReady, this._replaceComments.bind(this));
  }
  /**
   * This is called every time a new JSDoc comment block is found on a file. It validates
   * if the block uses `jsdoc-remove-next-tag` and saves it so it can be parsed later.
   *
   * @param {string} comment  The comment to analyze.
   * @access protected
   * @ignore
   */
  _readComment(comment) {
    if (comment.match(this._removeExpression)) {
      this._comments.push(comment);
    }
  }
  /**
   * This is called after all the JSDoc comments block for a file were found and the
   * plugin is ready to make changes.
   * The method takes all the comments that were found before and, if the comment includes
   * the `jsdoc-remove-next-tag` tag, it will remove the next lines until it finds a new
   * tag.
   *
   * @param {string} source  The code of the file being parsed.
   * @returns {string}
   * @access protected
   * @ignore
   */
  _replaceComments(source) {
    const result = this._comments.reduce((acc, comment) => {
      const lines = comment.split('\n');
      let removing = false;
      let removedAtLeastOneLine = false;
      /**
       * @type {string[]}
       */
      const newLinesAcc = [];
      const newLines = lines.reduce((sacc, line, index) => {
        if (index === lines.length - 1) {
          sacc.push(line);
          return sacc;
        }

        if (line.match(this._removeExpression)) {
          removing = true;
          removedAtLeastOneLine = false;
          return sacc;
        }

        if (removing && line.match(/^\s*\* @/)) {
          if (removedAtLeastOneLine) {
            removing = false;
          } else {
            removedAtLeastOneLine = true;
          }
        }

        if (!removing) {
          sacc.push(line);
        }

        return sacc;
      }, newLinesAcc);
      return acc.replace(comment, newLines.join('\n'));
    }, source);

    this._comments = [];
    return result;
  }
}

module.exports.RemoveTags = RemoveTags;