etnbrd/flx-compiler

View on GitHub
lib/graph-inspector-printer/graph.js

Summary

Maintainability
C
1 day
Test Coverage
var d3 = require('d3');

function getLocs(s) {
  if (typeof s.map !== 'function')
    return s;

  return s.map(function(i) {
    if (i.identifier)
      return i.identifier.loc;
    if (i.node)
      return i.node.loc;
    if (i.loc)
      return i.loc;

    return s;
  });
}

function tag(tagsByLine, type, loc) {
  tagsByLine[loc.start.line - 1]. push({ tagname: type, start: true, pos: loc.start.column});
  tagsByLine[loc.end.line - 1]. push({ tagname: type, start: false, pos: loc.end.column});
}

function tagCurr(a, b) {
  return function(c) {
    return tag(a, b ,c);
  };
}

function graph(ctx, code) {
  var tagsByLine = [], nbLines;
  code = String(code).split('\n');
  nbLines = code.length;

  for (var index = 0; index < nbLines; ++index)
    tagsByLine[index] = [];

  var variables = ctx.scopes
    .map(function (s) { return s.variables; })
    .reduce(function(a, b) { return a.concat(b); }, [])
    .map(function (s) { return { name: s.name, identifiers: getLocs(s.identifiers), references: getLocs(s.references), defs: getLocs(s.defs) }; })
    ;

  var references = ctx.scopes
    .map(function (s) { return s.references; })
    .reduce(function(a, b) { return a.concat(b); }, [])
    .map(function (s) { return s.identifier.loc; })
    ;

  references.forEach(tagCurr(tagsByLine, 'ref'));

  variables
    .forEach(function (s) {
      s.identifiers.forEach(tagCurr(tagsByLine, 'identifier'));
      s.references.forEach(tagCurr(tagsByLine, 'reference'));
      s.defs.forEach(tagCurr(tagsByLine, 'defs'));
    })
    ;

  tagsByLine = tagsByLine.map(function(l) {
    return l.sort(function(a, b) {
      if (a.pos < b.pos) return -1;
      if (a.pos > b.pos) return 1;
      if (!a.start && b.start) return -1;
      if (a.start && !b.start) return 1;

      return 0;
    });
  });

  var taggedCode = [];
  for (var i = 0; i < nbLines; ++i) {
    taggedCode[i] = '';
    for (var j = 0, l = code[i].length, k = 0, a = tagsByLine[i].length; j < l; ++j) {
      while (k < a && tagsByLine[i][k].pos === j && !tagsByLine[i][k].start) {
        taggedCode[i] += '</span>';
        ++k;
      }

      while (k < a && tagsByLine[i][k].pos === j && tagsByLine[i][k].start) {
        taggedCode[i] += '<span class="' + tagsByLine[i][k].tagname + '">';
        ++k;
      }

      taggedCode[i] += code[i][j];
    }
  }

  var usedVariables = ctx.scopes
    .map(function (s) { return s.references; })
    .reduce(function(a, b) { return a.concat(b); }, [])
    .map(function (s) {
      return {
        ref: s.identifier.loc.start.line,
        src: s.from.variables
          .filter(function(d) { return d.name === s.identifier.name; })
          .map(function(d) { return d.identifiers; })
          .reduce(function(a, b) { return a.concat(b); }, [])
          .map(function(d) { return d.loc.start.line; })
          [0]
      };
    })
    .filter(function (s) { return undefined !== s.src && s.ref !== s.src; })
    ;

  var lines = [[]];
  for (var i = 0; i < nbLines; ++i)
    lines[i+1] = [];

  usedVariables.forEach(function(s) { lines[s.ref].push(s.src); });

  lines.forEach(function(r, i) {
    if (r.length)
      console.log(i, r);
  });

  var margin = {top: 15, right: 40, bottom: 0, left: -40},
      width = 100 - margin.left - margin.right,
      height = (nbLines * 15) - margin.top - margin.bottom;

  var cluster = d3.layout.cluster()
      .size([height, width])
      // .sort(function(a, b) { return d3.ascending(a.name, b.name); })
      .value(function(d) { return 1; });

  var bundle = d3.layout.bundle();

  var line = d3.svg.line()
      .interpolate('bundle')
      .tension(.85)
      .x(function(d) { return d.y; })
      .y(function(d) { return d.x; });

  var svg = d3.select('body').append('svg')
      .attr('width', width + margin.left + margin.right)
      .attr('height', height + margin.top + margin.bottom)
      .attr('height', height + margin.top + margin.bottom)
    .append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

  var packages = {

    root: function(lines) {
      var nodes = [];

      lines.forEach(function(d, i) {
        nodes[i] = d;
      });

      return { name: '', children: nodes };
    },

    // Return a list of dependencies for the given array of nodes.
    dependencies: function(nodes) {
      var dependencies = [];

      nodes.forEach(function(d, t) {
        if (!d.children)
            d.forEach(function(s) {
              dependencies.push({source: nodes[s], target: nodes[t]});
            });
      });

      return dependencies;
    }
  };

  var nodes = cluster.nodes(packages.root(lines)),
      links = packages.dependencies(nodes);

  svg.selectAll('.link')
      .data(bundle(links))
    .enter().append('path')
      .attr('class', 'link')
      .attr('d', line);

  svg.selectAll('.node')
      .data(nodes)
    .enter().append('g')
      .attr('class', 'node')
      .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; })
    .append('text')
      .attr('dx', 8)
      .attr('dy', '.31em')
      .text(function(d) { return d.key; });

  return '<span style="float: left; width: 75px;"><svg xmlns="http://www.w3.org/2000/svg" version="1.1">' + d3.select('svg').html() + '</svg></span>\n'
      +  '<span style="float: left;"><code><pre>' + taggedCode.join('\n') + '</pre></code></span>';
}

// isomorphic JS
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
  module.exports = graph;