Home Reference Source Test

lib/match.js

var strip     = require('strip-indent');
var crypto    = require('crypto');
var NodeUtils = require('./nodeUtils');

/**
 *
 */
class Match {
  /**
   * Creates a new Match.
   *
   * @constructor
   *
   * @param {Node[][]} nodeArrays Multi-dimensional array of nodes
   */
  constructor(nodeArrays) {
    this.hash = this._getHash(nodeArrays);
    this.instances = this._generateInstances(nodeArrays);
  }

  /**
   * Populates each match instance with a lines property containing the
   * relevant source code lines.
   *
   * @param {object} fileContents The file paths and their contents
   */
   populateLines(fileContents) {
    this.instances.forEach(instance => {
      var lines = fileContents[instance.filename]
        .slice(instance.start.line - 1, instance.end.line)
        .join('\n');

      instance.lines = strip(lines);
    });
   }

  /**
   * Generates a hash for a match.
   *
   * @private
   *
   * @param   {Node[][]} nodeArrays   Multi-dimensional array of nodes
   * @returns {String}
   */
  _getHash(nodeArrays) {
    var str = nodeArrays
      .reduce((a, b) => a.concat(b))
      .map(node => node.name || node.type)
      .join(':');

    return crypto.createHash('sha1').update(str).digest('hex');
  }

  /**
   * Returns an array of objects containing the filename, start, end, and
   * lines associated with all instances of a match. Due to sibling traversal,
   * the end line must be searched for among the nodes, and isn't always
   * defined by the last node in the array.
   *
   * @private
   *
   * @param   {Node[][]} nodeArrays   Multi-dimensional array of nodes
   * @returns {object}
   */
  _generateInstances(nodeArrays) {
    return nodeArrays.map((nodes) => {
      var filename = nodes[0].loc.filename;

      var start = nodes.reduce((res, curr) => {
        return NodeUtils.isBefore(curr, res) ? curr : res;
      }).loc.start;

      // The end line requires more careful approximation so as not to
      // accidentally include a large number of irrelevant src lines
      // from a large node
      var base = nodes.map(node => node.loc.start)
        .reduce((res, curr) => (res.line > curr.line) ? res : curr);

      var last = nodes[nodes.length - 1];
      var lastEnd = last.loc.end;
      if (lastEnd.line > base.line && !NodeUtils.getChildren(last).length) {
        base = lastEnd;
      }

      var maxEnd = nodes.map(node => node.loc.end)
        .reduce((res, curr) => (res.line > curr.line) ? res : curr);
      var end = maxEnd.line - base.line <= 2 ? maxEnd : base;

      return {filename, start, end};
    });
  }
}

module.exports = Match;