_analyze() {
    var keys = Object.keys(this._map)
      .filter(key => this._map[key].length >= this._minInstances);

    // Need to use a stable sort to ensure parent nodes are traversed
    // before children when lengths are equal
    var sortedKeys = stable(keys, (a, b) => {
      return this._map[b].length - this._map[a].length;
    });

    for (let key of sortedKeys) {
      if (!this._map[key] || this._map[key].length < this._minInstances) {
        continue;
      }

      let nodeArrays = this._map[key].slice(0);
      this._omitOverlappingInstances(nodeArrays);

      // groups will be of type Node[][][]
      let groups = [nodeArrays];
      if (this._identifiers) {
        groups = this._groupByMatchingIdentifiers(groups);
      }
      if (this._literals) {
        groups = this._groupByMatchingLiterals(groups);
      }

      for (let i = 0; i < groups.length; i++) {
        if (groups[i].length < this._minInstances) continue;

        this._expand(groups[i]);
        let match = new Match(groups[i]);
        match.populateLines(this._fileContents);
        this.emit('match', match);
        this._prune(groups[i]);
      }
    }
  }
Traverses the keys at which the various nodes are stored. A key containing
an array of more than a single entry indicates a potential match. The nodes
are then grouped if identifier matching is enabled. A match results in the
relevant nodes being removed from any future results. This pruning ensures
that we only include the greatest common parent in a set of matches.

@private

@fires Inspector#match