text: /**
   * Provides methods for augmenting the parse results based on their content.
   * @module jsdoc/augment
   */
  
  const _ = require('lodash');
  const { fromParts, SCOPE, toParts } = require('@jsdoc/core').name;
  const jsdoc = {
    doclet: require('jsdoc/doclet'),
  };
  
  const hasOwnProp = Object.prototype.hasOwnProperty;
  
  function mapDependencies(index, propertyName) {
    const dependencies = {};
    let doc;
    let doclets;
    const kinds = ['class', 'external', 'interface', 'mixin'];
    let len = 0;
  
    Object.keys(index).forEach((indexName) => {
      doclets = index[indexName];
      for (let i = 0, ii = doclets.length; i < ii; i++) {
        doc = doclets[i];
        if (kinds.includes(doc.kind)) {
          dependencies[indexName] = {};
          if (hasOwnProp.call(doc, propertyName)) {
            len = doc[propertyName].length;
            for (let j = 0; j < len; j++) {
              dependencies[indexName][doc[propertyName][j]] = true;
            }
          }
        }
      }
    });
  
    return dependencies;
  }
  
  class Sorter {
    constructor(dependencies) {
      this.dependencies = dependencies;
      this.visited = {};
      this.sorted = [];
    }
  
    visit(key) {
      if (!(key in this.visited)) {
        this.visited[key] = true;
  
        if (this.dependencies[key]) {
          Object.keys(this.dependencies[key]).forEach((path) => {
            this.visit(path);
          });
        }
  
        this.sorted.push(key);
      }
    }
  
    sort() {
      Object.keys(this.dependencies).forEach((key) => {
        this.visit(key);
      });
  
      return this.sorted;
    }
  }
  
  function sort(dependencies) {
    const sorter = new Sorter(dependencies);
  
    return sorter.sort();
  }
  
  function getMembers(longname, { index }, scopes) {
    const memberof = index.memberof[longname] || [];
    const members = [];
  
    memberof.forEach((candidate) => {
      if (scopes.includes(candidate.scope)) {
        members.push(candidate);
      }
    });
  
    return members;
  }
  
  function getDocumentedLongname(longname, { index }) {
    const doclets = index.documented[longname] || [];
  
    return doclets[doclets.length - 1];
  }
  
  function addDocletProperty(doclets, propName, value) {
    for (let i = 0, l = doclets.length; i < l; i++) {
      doclets[i][propName] = value;
    }
  }
  
  function reparentDoclet({ longname }, child) {
    const parts = toParts(child.longname);
  
    parts.memberof = longname;
    child.memberof = longname;
    child.longname = fromParts(parts);
  }
  
  function parentIsClass({ kind }) {
    return kind === 'class';
  }
  
  function staticToInstance(doclet) {
    const parts = toParts(doclet.longname);
  
    parts.scope = SCOPE.PUNC.INSTANCE;
    doclet.longname = fromParts(parts);
    doclet.scope = SCOPE.NAMES.INSTANCE;
  }
  
  /**
   * Update the list of doclets to be added to another symbol.
   *
   * We add only one doclet per longname. For example: If `ClassA` inherits from two classes that both
   * use the same method name, `ClassA` gets docs for one method rather than two.
   *
   * Also, the last symbol wins for any given longname. For example: If you write `@extends Class1
   * @extends Class2`, and both classes have an instance method called `myMethod`, you get the docs
   * from `Class2#myMethod`.
   *
   * @private
   * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added.
   * @param {Array.<module:jsdoc/doclet.Doclet>} additions - An array of doclets that will be added to
   * another symbol.
   * @param {Object.<string, number>} indexes - A dictionary of indexes into the `additions` array.
   * Each key is a longname, and each value is the index of the longname's doclet.
   * @return {void}
   */
  function updateAddedDoclets(doclet, additions, indexes) {
    if (typeof indexes[doclet.longname] !== 'undefined') {
      // replace the existing doclet
      additions[indexes[doclet.longname]] = doclet;
    } else {
      // add the doclet to the array, and track its index
      additions.push(doclet);
      indexes[doclet.longname] = additions.length - 1;
    }
  }
  
  /**
   * Update the index of doclets whose `undocumented` property is not `true`.
   *
   * @private
   * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
   * @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} documented - The index of doclets
   * whose `undocumented` property is not `true`.
   * @return {void}
   */
  function updateDocumentedDoclets(doclet, documented) {
    if (!hasOwnProp.call(documented, doclet.longname)) {
      documented[doclet.longname] = [];
    }
  
    documented[doclet.longname].push(doclet);
  }
  
  /**
   * Update the index of doclets with a `memberof` value.
   *
   * @private
   * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
   * @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} memberof - The index of doclets
   * with a `memberof` value.
   * @return {void}
   */
  function updateMemberofDoclets(doclet, memberof) {
    if (doclet.memberof) {
      if (!hasOwnProp.call(memberof, doclet.memberof)) {
        memberof[doclet.memberof] = [];
      }
  
      memberof[doclet.memberof].push(doclet);
    }
  }
  
  function explicitlyInherits(doclets) {
    let doclet;
    let inherits = false;
  
    for (let i = 0, l = doclets.length; i < l; i++) {
      doclet = doclets[i];
      if (typeof doclet.inheritdoc !== 'undefined' || typeof doclet.override !== 'undefined') {
        inherits = true;
        break;
      }
    }
  
    return inherits;
  }
  
  function changeMemberof(longname, newMemberof) {
    const atoms = toParts(longname);
  
    atoms.memberof = newMemberof;
  
    return fromParts(atoms);
  }
  
  // TODO: try to reduce overlap with similar methods
  function getInheritedAdditions(doclets, docs, { documented, memberof }) {
    let additionIndexes;
    const additions = [];
    let childDoclet;
    let childLongname;
    let doc;
    let parentDoclet;
    let parentMembers;
    let parents;
    let member;
    let parts;
  
    // doclets will be undefined if the inherited symbol isn't documented
    doclets = doclets || [];
  
    for (let i = 0, ii = doclets.length; i < ii; i++) {
      doc = doclets[i];
      parents = doc.augments;
  
      if (parents && (doc.kind === 'class' || doc.kind === 'interface')) {
        // reset the lookup table of added doclet indexes by longname
        additionIndexes = {};
  
        for (let j = 0, jj = parents.length; j < jj; j++) {
          parentMembers = getMembers(parents[j], docs, ['instance']);
  
          for (let k = 0, kk = parentMembers.length; k < kk; k++) {
            parentDoclet = parentMembers[k];
  
            // We only care about symbols that are documented.
            if (parentDoclet.undocumented) {
              continue;
            }
  
            childLongname = changeMemberof(parentDoclet.longname, doc.longname);
            childDoclet = getDocumentedLongname(childLongname, docs) || {};
  
            // We don't want to fold in properties from the child doclet if it had an
            // `@inheritdoc` tag.
            if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
              childDoclet = {};
            }
  
            member = jsdoc.doclet.combine(childDoclet, parentDoclet);
  
            if (!member.inherited) {
              member.inherits = member.longname;
            }
            member.inherited = true;
  
            member.memberof = doc.longname;
            parts = toParts(member.longname);
            parts.memberof = doc.longname;
            member.longname = fromParts(parts);
  
            // Indicate what the descendant is overriding. (We only care about the closest
            // ancestor. For classes A > B > C, if B#a overrides A#a, and C#a inherits B#a,
            // we don't want the doclet for C#a to say that it overrides A#a.)
            if (hasOwnProp.call(docs.index.longname, member.longname)) {
              member.overrides = parentDoclet.longname;
            } else {
              delete member.overrides;
            }
  
            // Add the ancestor's docs unless the descendant overrides the ancestor AND
            // documents the override.
            if (!hasOwnProp.call(documented, member.longname)) {
              updateAddedDoclets(member, additions, additionIndexes);
              updateDocumentedDoclets(member, documented);
              updateMemberofDoclets(member, memberof);
            }
            // If the descendant used an @inheritdoc or @override tag, add the ancestor's
            // docs, and ignore the existing doclets.
            else if (explicitlyInherits(documented[member.longname])) {
              // Ignore any existing doclets. (This is safe because we only get here if
              // `member.longname` is an own property of `documented`.)
              addDocletProperty(documented[member.longname], 'ignore', true);
  
              updateAddedDoclets(member, additions, additionIndexes);
              updateDocumentedDoclets(member, documented);
              updateMemberofDoclets(member, memberof);
  
              // Remove property that's no longer accurate.
              if (member.virtual) {
                delete member.virtual;
              }
              // Remove properties that we no longer need.
              if (member.inheritdoc) {
                delete member.inheritdoc;
              }
              if (member.override) {
                delete member.override;
              }
            }
            // If the descendant overrides the ancestor and documents the override,
            // update the doclets to indicate what the descendant is overriding.
            else {
              addDocletProperty(documented[member.longname], 'overrides', parentDoclet.longname);
            }
          }
        }
      }
    }
  
    return additions;
  }
  
  function updateMixes(mixedDoclet, mixedLongname) {
    let idx;
    let mixedName;
    let names;
  
    // take the fast path if there's no array of mixed-in longnames
    if (!mixedDoclet.mixes) {
      mixedDoclet.mixes = [mixedLongname];
    } else {
      // find the short name of the longname we're mixing in
      mixedName = toParts(mixedLongname).name;
      // find the short name of each previously mixed-in symbol
      // TODO: why do we run a map if we always shorten the same value? this looks like a bug...
      names = mixedDoclet.mixes.map(() => toParts(mixedDoclet.longname).name);
  
      // if we're mixing `myMethod` into `MixinC` from `MixinB`, and `MixinB` had the method mixed
      // in from `MixinA`, don't show `MixinA.myMethod` in the `mixes` list
      idx = names.indexOf(mixedName);
      if (idx !== -1) {
        mixedDoclet.mixes.splice(idx, 1);
      }
  
      mixedDoclet.mixes.push(mixedLongname);
    }
  }
  
  // TODO: try to reduce overlap with similar methods
  function getMixedInAdditions(mixinDoclets, allDoclets, { documented, memberof }) {
    let additionIndexes;
    const additions = [];
    const commentedDoclets = documented;
    let doclet;
    let mixedDoclet;
    let mixedDoclets;
    let mixes;
  
    // mixinDoclets will be undefined if the mixed-in symbol isn't documented
    mixinDoclets = mixinDoclets || [];
  
    for (let i = 0, ii = mixinDoclets.length; i < ii; i++) {
      doclet = mixinDoclets[i];
      mixes = doclet.mixes;
  
      if (mixes) {
        // reset the lookup table of added doclet indexes by longname
        additionIndexes = {};
  
        for (let j = 0, jj = mixes.length; j < jj; j++) {
          mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
  
          for (let k = 0, kk = mixedDoclets.length; k < kk; k++) {
            // We only care about symbols that are documented.
            if (mixedDoclets[k].undocumented) {
              continue;
            }
  
            mixedDoclet = _.cloneDeep(mixedDoclets[k]);
  
            updateMixes(mixedDoclet, mixedDoclet.longname);
            mixedDoclet.mixed = true;
  
            reparentDoclet(doclet, mixedDoclet);
  
            // if we're mixing into a class, treat the mixed-in symbol as an instance member
            if (parentIsClass(doclet)) {
              staticToInstance(mixedDoclet);
            }
  
            updateAddedDoclets(mixedDoclet, additions, additionIndexes);
            updateDocumentedDoclets(mixedDoclet, commentedDoclets);
            updateMemberofDoclets(mixedDoclet, memberof);
          }
        }
      }
    }
  
    return additions;
  }
  
  function updateImplements(implDoclets, implementedLongname) {
    if (!Array.isArray(implDoclets)) {
      implDoclets = [implDoclets];
    }
  
    implDoclets.forEach((implDoclet) => {
      if (!hasOwnProp.call(implDoclet, 'implements')) {
        implDoclet.implements = [];
      }
  
      if (!implDoclet.implements.includes(implementedLongname)) {
        implDoclet.implements.push(implementedLongname);
      }
    });
  }
  
  // TODO: try to reduce overlap with similar methods
  function getImplementedAdditions(implDoclets, allDoclets, { documented, memberof }) {
    let additionIndexes;
    const additions = [];
    let childDoclet;
    let childLongname;
    const commentedDoclets = documented;
    let doclet;
    let implementations;
    let implExists;
    let implementationDoclet;
    let interfaceDoclets;
    let parentDoclet;
  
    // interfaceDoclets will be undefined if the implemented symbol isn't documented
    implDoclets = implDoclets || [];
  
    for (let i = 0, ii = implDoclets.length; i < ii; i++) {
      doclet = implDoclets[i];
      implementations = doclet.implements;
  
      if (implementations) {
        // reset the lookup table of added doclet indexes by longname
        additionIndexes = {};
  
        for (let j = 0, jj = implementations.length; j < jj; j++) {
          interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']);
  
          for (let k = 0, kk = interfaceDoclets.length; k < kk; k++) {
            parentDoclet = interfaceDoclets[k];
  
            // We only care about symbols that are documented.
            if (parentDoclet.undocumented) {
              continue;
            }
  
            childLongname = changeMemberof(parentDoclet.longname, doclet.longname);
            childDoclet = getDocumentedLongname(childLongname, allDoclets) || {};
  
            // We don't want to fold in properties from the child doclet if it had an
            // `@inheritdoc` tag.
            if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
              childDoclet = {};
            }
  
            implementationDoclet = jsdoc.doclet.combine(childDoclet, parentDoclet);
  
            reparentDoclet(doclet, implementationDoclet);
            updateImplements(implementationDoclet, parentDoclet.longname);
  
            // If there's no implementation, move along.
            implExists = hasOwnProp.call(allDoclets.index.longname, implementationDoclet.longname);
            if (!implExists) {
              continue;
            }
  
            // Add the interface's docs unless the implementation is already documented.
            if (!hasOwnProp.call(commentedDoclets, implementationDoclet.longname)) {
              updateAddedDoclets(implementationDoclet, additions, additionIndexes);
              updateDocumentedDoclets(implementationDoclet, commentedDoclets);
              updateMemberofDoclets(implementationDoclet, memberof);
            }
            // If the implementation used an @inheritdoc or @override tag, add the
            // interface's docs, and ignore the existing doclets.
            else if (explicitlyInherits(commentedDoclets[implementationDoclet.longname])) {
              // Ignore any existing doclets. (This is safe because we only get here if
              // `implementationDoclet.longname` is an own property of
              // `commentedDoclets`.)
              addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore', true);
  
              updateAddedDoclets(implementationDoclet, additions, additionIndexes);
              updateDocumentedDoclets(implementationDoclet, commentedDoclets);
              updateMemberofDoclets(implementationDoclet, memberof);
  
              // Remove property that's no longer accurate.
              if (implementationDoclet.virtual) {
                delete implementationDoclet.virtual;
              }
              // Remove properties that we no longer need.
              if (implementationDoclet.inheritdoc) {
                delete implementationDoclet.inheritdoc;
              }
              if (implementationDoclet.override) {
                delete implementationDoclet.override;
              }
            }
            // If there's an implementation, and it's documented, update the doclets to
            // indicate what the implementation is implementing.
            else {
              updateImplements(
                commentedDoclets[implementationDoclet.longname],
                parentDoclet.longname
              );
            }
          }
        }
      }
    }
  
    return additions;
  }
  
  function augment(doclets, propertyName, docletFinder, jsdocDeps) {
    const index = doclets.index.longname;
    const dependencies = sort(mapDependencies(index, propertyName));
  
    dependencies.forEach((depName) => {
      const additions = docletFinder(index[depName], doclets, doclets.index, jsdocDeps);
  
      additions.forEach((addition) => {
        const longname = addition.longname;
  
        if (!hasOwnProp.call(index, longname)) {
          index[longname] = [];
        }
        index[longname].push(addition);
        doclets.push(addition);
      });
    });
  }
  
  /**
   * Add doclets to reflect class inheritance.
   *
   * For example, if `ClassA` has the instance method `myMethod`, and `ClassB` inherits from `ClassA`,
   * calling this method creates a new doclet for `ClassB#myMethod`.
   *
   * @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
   * @param {!Object} doclets.index - The doclet index.
   * @return {void}
   */
  exports.addInherited = (doclets) => {
    augment(doclets, 'augments', getInheritedAdditions);
  };
  
  /**
   * Add doclets to reflect mixins. When a symbol is mixed into a class, the class' version of the
   * mixed-in symbol is treated as an instance member.
   *
   * For example:
   *
   * + If `MixinA` has the static method `myMethod`, and `MixinB` mixes `MixinA`, calling this method
   * creates a new doclet for the static method `MixinB.myMethod`.
   * + If `MixinA` has the static method `myMethod`, and `ClassA` mixes `MixinA`, calling this method
   * creates a new doclet for the instance method `ClassA#myMethod`.
   *
   * @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
   * @param {!Object} doclets.index - The doclet index.
   * @return {void}
   */
  exports.addMixedIn = (doclets) => {
    augment(doclets, 'mixes', getMixedInAdditions);
  };
  
  /**
   * Add and update doclets to reflect implementations of interfaces.
   *
   * For example, if `InterfaceA` has the instance method `myMethod`, and `ClassA` implements
   * `InterfaceA`, calling this method does the following:
   *
   * + Updates `InterfaceA` to indicate that it is implemented by `ClassA`
   * + Updates `InterfaceA#myMethod` to indicate that it is implemented by `ClassA#myMethod`
   * + Updates `ClassA#myMethod` to indicate that it implements `InterfaceA#myMethod`
   *
   * If `ClassA#myMethod` used the `@override` or `@inheritdoc` tag, calling this method would also
   * generate a new doclet that reflects the interface's documentation for `InterfaceA#myMethod`.
   *
   * @param {!Array.<module:jsdoc/doclet.Doclet>} docs - The doclets generated by JSDoc.
   * @param {!Object} doclets.index - The doclet index.
   * @return {void}
   */
  exports.addImplemented = (doclets) => {
    augment(doclets, 'implements', getImplementedAdditions);
  };
  
  /**
   * Add and update doclets to reflect all of the following:
   *
   * + Inherited classes
   * + Mixins
   * + Interface implementations
   *
   * Calling this method is equivalent to calling all other methods exported by this module.
   *
   * @return {void}
   */
  exports.augmentAll = (doclets) => {
    exports.addMixedIn(doclets);
    exports.addImplemented(doclets);
    exports.addInherited(doclets);
    // look for implemented doclets again, in case we inherited an interface
    exports.addImplemented(doclets);
  };
  