All files / src/abap/flow flow_graph.ts

89.63% Statements 147/164
95% Branches 38/40
88.23% Functions 15/17
89.63% Lines 147/164

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 1641x 1x 1x 1x 1x 1x 1x 94x 94x 94x 94x 94x 1x 1x 155x 155x 1x 1x 211x 211x 1x 1x 727x 604x 604x 727x 727x 1x 1x 265x     265x 265x 210x 210x 265x 1x 1x 504x 504x 3316x 3887x 3887x 3316x 504x 504x 1x 1x                       1x 1x 32x 32x 273x 273x 273x 32x 32x 1x 1x 62x 62x 1x 1x 1x 62x     62x 62x 62x 62x 1x 1x     1x 1x 32x 32x 149x 149x 32x 32x 1x 1x 32x 32x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 188x 188x 1575x 191x 191x 1575x 188x 188x 1x 1x 188x 188x 1575x 158x 158x 1575x 188x 188x 1x 1x 1x 31x 272x 86x 86x 186x 186x 272x 116x 116x 138x 138x 116x 118x 118x 116x 138x 140x 140x 138x 116x 186x 272x 8x 8x 8x 8x 272x 31x 31x 31x 1x
export class FlowGraph {
  private edges: {[from: string]: {[to: string]: boolean}};
  private readonly startNode: string;
  private readonly endNode: string;
  private label: string;
 
  public constructor(counter: number) {
    this.edges = {};
    this.label = "undefined";
    this.startNode = "start#" + counter;
    this.endNode = "end#" + counter;
  }
 
  public getStart(): string {
    return this.startNode;
  }
 
  public getEnd(): string {
    return this.endNode;
  }
 
  public addEdge(from: string, to: string) {
    if (this.edges[from] === undefined) {
      this.edges[from] = {};
    }
    this.edges[from][to] = true;
  }
 
  public removeEdge(from: string, to: string) {
    if (this.edges[from] === undefined) {
      return;
    }
    delete this.edges[from][to];
    if (Object.keys(this.edges[from]).length === 0) {
      delete this.edges[from];
    }
  }
 
  public listEdges() {
    const list: {from: string, to: string}[] = [];
    for (const from of Object.keys(this.edges)) {
      for (const to of Object.keys(this.edges[from])) {
        list.push({from, to});
      }
    }
    return list;
  }
 
  public listInto(to: string, skipStart = true): string[] {
    const ret: string[] = [];
    for (const e of this.listEdges()) {
      if (skipStart === true && e.from === this.getStart()) {
        continue;
      }
      if (e.to === to) {
        ret.push(e.from);
      }
    }
    return ret;
  }
 
  public listNodes() {
    const set = new Set<string>();
    for (const l of this.listEdges()) {
      set.add(l.from);
      set.add(l.to);
    }
    return Array.from(set.values());
  }
 
  public hasEdges(): boolean {
    return Object.keys(this.edges).length > 0;
  }
 
  /** return value: end node of to graph */
  public addGraph(from: string, to: FlowGraph): string {
    if (to.hasEdges() === false) {
      return from;
    }
    this.addEdge(from, to.getStart());
    to.listEdges().forEach(e => this.addEdge(e.from, e.to));
    return to.getEnd();
  }
 
  public toJSON(): string {
    return JSON.stringify(this.edges);
  }
 
  public toTextEdges(): string {
    let graph = "";
    for (const l of this.listEdges()) {
      graph += `"${l.from}" -> "${l.to}";\n`;
    }
    return graph.trim();
  }
 
  public setLabel(label: string) {
    this.label = label;
  }
 
  public toDigraph(): string {
    return `digraph G {
labelloc="t";
label="${this.label}";
graph [fontname = "helvetica"];
node [fontname = "helvetica", shape="box"];
edge [fontname = "helvetica"];
${this.toTextEdges()}
}`;
  }
 
  public listSources(node: string): string[] {
    const set = new Set<string>();
    for (const l of this.listEdges()) {
      if (node === l.to) {
        set.add(l.from);
      }
    }
    return Array.from(set.values());
  }
 
  public listTargets(node: string): string[] {
    const set = new Set<string>();
    for (const l of this.listEdges()) {
      if (node === l.from) {
        set.add(l.to);
      }
    }
    return Array.from(set.values());
  }
 
  /** removes all nodes containing "#" that have one in-going and one out-going edge */
  public reduce() {
    for (const node of this.listNodes()) {
      if (node.includes("#") === false) {
        continue;
      }
      const sources = this.listSources(node);
      const targets = this.listTargets(node);
      if (sources.length > 0 && targets.length > 0) {
        // hash node in the middle of the graph
        for (const s of sources) {
          this.removeEdge(s, node);
        }
        for (const t of targets) {
          this.removeEdge(node, t);
        }
        for (const s of sources) {
          for (const t of targets) {
            this.addEdge(s, t);
          }
        }
      }
 
      if (node.startsWith("end#") && sources.length === 0) {
        for (const t of targets) {
          this.removeEdge(node, t);
        }
      }
    }
 
    return this;
  }
}