@snyk/dep-graph
Version:
Snyk dependency graph library
486 lines • 14.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Graph = void 0;
/* eslint-disable prefer-rest-params */
/* eslint-disable @typescript-eslint/no-this-alias */
const constant = require("lodash.constant");
const each = require("lodash.foreach");
const _filter = require("lodash.foreach");
const isEmpty = require("lodash.isempty");
const isFunction = require("lodash.isfunction");
const isUndefined = require("lodash.isundefined");
const reduce = require("lodash.reduce");
const union = require("lodash.union");
const values = require("lodash.values");
const DEFAULT_EDGE_NAME = '\x00';
const GRAPH_NODE = '\x00';
const EDGE_KEY_DELIM = '\x01';
// Implementation notes:
//
// * Node id query functions should return string ids for the nodes
// * Edge id query functions should return an "edgeObj", edge object, that is
// composed of enough information to uniquely identify an edge: {v, w, name}.
// * Internally we use an "edgeId", a stringified form of the edgeObj, to
// reference edges. This is because we need a performant way to look these
// edges up and, object properties, which have string keys, are the closest
// we're going to get to a performant hashtable in JavaScript.
class Graph {
constructor(opts) {
var _a, _b, _c;
/* Number of nodes in the graph. Should only be changed by the implementation. */
this._nodeCount = 0;
/* Number of edges in the graph. Should only be changed by the implementation. */
this._edgeCount = 0;
this._isDirected = (_a = opts === null || opts === void 0 ? void 0 : opts.directed) !== null && _a !== void 0 ? _a : true;
this._isMultigraph = (_b = opts === null || opts === void 0 ? void 0 : opts.multigraph) !== null && _b !== void 0 ? _b : false;
this._isCompound = (_c = opts === null || opts === void 0 ? void 0 : opts.compound) !== null && _c !== void 0 ? _c : false;
// Label for the graph itself
this._label = undefined;
// Defaults to be set when creating a new node
this._defaultNodeLabelFn = constant(undefined);
// Defaults to be set when creating a new edge
this._defaultEdgeLabelFn = constant(undefined);
// v -> label
this._nodes = {};
if (this._isCompound) {
// v -> parent
this._parent = {};
// v -> children
this._children = {};
this._children[GRAPH_NODE] = {};
}
// v -> edgeObj
this._in = {};
// u -> v -> Number
this._preds = {};
// v -> edgeObj
this._out = {};
// v -> w -> Number
this._sucs = {};
// e -> edgeObj
this._edgeObjs = {};
// e -> label
this._edgeLabels = {};
}
/* === Graph functions ========= */
isDirected() {
return this._isDirected;
}
isMultigraph() {
return this._isMultigraph;
}
isCompound() {
return this._isCompound;
}
setGraph(label) {
this._label = label;
return this;
}
graph() {
return this._label;
}
/* === Node functions ========== */
setDefaultNodeLabel(newDefault) {
if (!isFunction(newDefault)) {
newDefault = constant(newDefault);
}
this._defaultNodeLabelFn = newDefault;
return this;
}
nodeCount() {
return this._nodeCount;
}
nodes() {
return Object.keys(this._nodes);
}
sources() {
const self = this;
return _filter(this.nodes(), function (v) {
return isEmpty(self._in[v]);
});
}
sinks() {
const self = this;
return _filter(this.nodes(), function (v) {
return isEmpty(self._out[v]);
});
}
setNodes(vs, value) {
const args = arguments;
const self = this;
each(vs, function (v) {
if (args.length > 1) {
self.setNode(v, value);
}
else {
self.setNode(v);
}
});
return this;
}
setNode(v, value) {
if (v in this._nodes) {
if (arguments.length > 1) {
this._nodes[v] = value;
}
return this;
}
this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
if (this._isCompound) {
this._parent[v] = GRAPH_NODE;
this._children[v] = {};
this._children[GRAPH_NODE][v] = true;
}
this._in[v] = {};
this._preds[v] = {};
this._out[v] = {};
this._sucs[v] = {};
++this._nodeCount;
return this;
}
node(v) {
return this._nodes[v];
}
hasNode(v) {
return v in this._nodes;
}
removeNode(v) {
const self = this;
if (v in this._nodes) {
const removeEdge = function (e) {
self.removeEdge(self._edgeObjs[e]);
};
delete this._nodes[v];
if (this._isCompound) {
this._removeFromParentsChildList(v);
delete this._parent[v];
each(this.children(v), function (child) {
self.setParent(child);
});
delete this._children[v];
}
each(Object.keys(this._in[v]), removeEdge);
delete this._in[v];
delete this._preds[v];
each(Object.keys(this._out[v]), removeEdge);
delete this._out[v];
delete this._sucs[v];
--this._nodeCount;
}
return this;
}
setParent(v, parent) {
if (!this._isCompound) {
throw new Error('Cannot set parent in a non-compound graph');
}
if (isUndefined(parent)) {
parent = GRAPH_NODE;
}
else {
// Coerce parent to string
parent += '';
for (let ancestor = parent; !isUndefined(ancestor); ancestor = this.parent(ancestor)) {
if (ancestor === v) {
throw new Error('Setting ' +
parent +
' as parent of ' +
v +
' would create a cycle');
}
}
this.setNode(parent);
}
this.setNode(v);
this._removeFromParentsChildList(v);
this._parent[v] = parent;
this._children[parent][v] = true;
return this;
}
_removeFromParentsChildList(v) {
delete this._children[this._parent[v]][v];
}
parent(v) {
if (this._isCompound) {
const parent = this._parent[v];
if (parent !== GRAPH_NODE) {
return parent;
}
}
}
children(v) {
if (isUndefined(v)) {
v = GRAPH_NODE;
}
if (this._isCompound) {
const children = this._children[v];
if (children) {
return Object.keys(children);
}
}
else if (v === GRAPH_NODE) {
return this.nodes();
}
else if (this.hasNode(v)) {
return [];
}
}
predecessors(v) {
const predsV = this._preds[v];
if (predsV) {
return Object.keys(predsV);
}
}
successors(v) {
const sucsV = this._sucs[v];
if (sucsV) {
return Object.keys(sucsV);
}
}
neighbors(v) {
const preds = this.predecessors(v);
if (preds) {
return union(preds, this.successors(v));
}
}
isLeaf(v) {
let neighbors;
if (this.isDirected()) {
neighbors = this.successors(v);
}
else {
neighbors = this.neighbors(v);
}
return neighbors.length === 0;
}
filterNodes(filter) {
const copy = new Graph({
directed: this._isDirected,
multigraph: this._isMultigraph,
compound: this._isCompound,
});
copy.setGraph(this.graph());
const self = this;
each(this._nodes, function (value, v) {
if (filter(v)) {
copy.setNode(v, value);
}
});
each(this._edgeObjs, function (e) {
if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
copy.setEdge(e, self.edge(e));
}
});
const parents = {};
function findParent(v) {
const parent = self.parent(v);
if (parent === undefined || copy.hasNode(parent)) {
parents[v] = parent;
return parent;
}
else if (parent in parents) {
return parents[parent];
}
else {
return findParent(parent);
}
}
if (this._isCompound) {
each(copy.nodes(), function (v) {
copy.setParent(v, findParent(v));
});
}
return copy;
}
/* === Edge functions ========== */
setDefaultEdgeLabel(newDefault) {
if (!isFunction(newDefault)) {
newDefault = constant(newDefault);
}
this._defaultEdgeLabelFn = newDefault;
return this;
}
edgeCount() {
return this._edgeCount;
}
edges() {
return values(this._edgeObjs);
}
setPath(vs, value) {
const self = this;
const args = arguments;
reduce(vs, function (v, w) {
if (args.length > 1) {
self.setEdge(v, w, value);
}
else {
self.setEdge(v, w);
}
return w;
});
return this;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setEdge(...args) {
let v, w, name, value;
let valueSpecified = false;
const arg0 = arguments[0];
if (typeof arg0 === 'object' && arg0 !== null && 'v' in arg0) {
v = arg0.v;
w = arg0.w;
name = arg0.name;
if (arguments.length === 2) {
value = arguments[1];
valueSpecified = true;
}
}
else {
v = arg0;
w = arguments[1];
name = arguments[3];
if (arguments.length > 2) {
value = arguments[2];
valueSpecified = true;
}
}
v = '' + v;
w = '' + w;
if (!isUndefined(name)) {
name = '' + name;
}
const e = edgeArgsToId(this._isDirected, v, w, name);
if (e in this._edgeLabels) {
if (valueSpecified) {
this._edgeLabels[e] = value;
}
return this;
}
if (!isUndefined(name) && !this._isMultigraph) {
throw new Error('Cannot set a named edge when isMultigraph = false');
}
// It didn't exist, so we need to create it.
// First ensure the nodes exist.
this.setNode(v);
this.setNode(w);
this._edgeLabels[e] = valueSpecified
? value
: this._defaultEdgeLabelFn(v, w, name);
const edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
// Ensure we add undirected edges in a consistent way.
v = edgeObj.v;
w = edgeObj.w;
Object.freeze(edgeObj);
this._edgeObjs[e] = edgeObj;
incrementOrInitEntry(this._preds[w], v);
incrementOrInitEntry(this._sucs[v], w);
this._in[w][e] = edgeObj;
this._out[v][e] = edgeObj;
this._edgeCount++;
return this;
}
edge(v, w, name) {
const e = arguments.length === 1
? edgeObjToId(this._isDirected, arguments[0])
: edgeArgsToId(this._isDirected, v, w, name);
return this._edgeLabels[e];
}
hasEdge(v, w, name) {
const e = arguments.length === 1
? edgeObjToId(this._isDirected, arguments[0])
: edgeArgsToId(this._isDirected, v, w, name);
return e in this._edgeLabels;
}
removeEdge(v, w, name) {
const e = arguments.length === 1
? edgeObjToId(this._isDirected, arguments[0])
: edgeArgsToId(this._isDirected, v, w, name);
const edge = this._edgeObjs[e];
if (edge) {
v = edge.v;
w = edge.w;
delete this._edgeLabels[e];
delete this._edgeObjs[e];
decrementOrRemoveEntry(this._preds[w], v);
decrementOrRemoveEntry(this._sucs[v], w);
delete this._in[w][e];
delete this._out[v][e];
this._edgeCount--;
}
return this;
}
inEdges(v, u) {
const inV = this._in[v];
if (inV) {
const edges = values(inV);
if (!u) {
return edges;
}
return _filter(edges, function (edge) {
return edge.v === u;
});
}
}
outEdges(v, w) {
const outV = this._out[v];
if (outV) {
const edges = values(outV);
if (!w) {
return edges;
}
return _filter(edges, function (edge) {
return edge.w === w;
});
}
}
nodeEdges(v, w) {
const inEdges = this.inEdges(v, w);
if (inEdges) {
return inEdges.concat(this.outEdges(v, w));
}
}
}
exports.Graph = Graph;
function incrementOrInitEntry(map, k) {
if (map[k]) {
map[k]++;
}
else {
map[k] = 1;
}
}
function decrementOrRemoveEntry(map, k) {
if (!--map[k]) {
delete map[k];
}
}
function edgeArgsToId(isDirected, v_, w_, name) {
let v = '' + v_;
let w = '' + w_;
if (!isDirected && v > w) {
const tmp = v;
v = w;
w = tmp;
}
return (v +
EDGE_KEY_DELIM +
w +
EDGE_KEY_DELIM +
(isUndefined(name) ? DEFAULT_EDGE_NAME : name));
}
function edgeArgsToObj(isDirected, v_, w_, name) {
let v = '' + v_;
let w = '' + w_;
if (!isDirected && v > w) {
const tmp = v;
v = w;
w = tmp;
}
const edgeObj = { v: v, w: w };
if (name) {
edgeObj.name = name;
}
return edgeObj;
}
function edgeObjToId(isDirected, edgeObj) {
return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
}
//# sourceMappingURL=graph.js.map