All files / src/rules cyclic_oo.ts

93.43% Statements 128/137
86.36% Branches 38/44
100% Functions 9/9
93.43% Lines 128/137

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 1371x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 17845x 17845x 17845x 17845x 17845x 17845x 17845x 17845x 1x 8924x 8924x 8924x 8924x 8924x 8924x 69035x 69035x 69035x 69035x 69035x 69035x 69035x 8924x 8924x 8485x 8485x 8924x 8924x 202x 202x     202x 8924x 8924x 205x 205x 205x 44x 44x   44x   44x     44x 44x 205x 21x 21x   21x     21x 21x 205x 205x 8924x 8924x 260x 197x 197x 63x 63x 260x 3x 3x 60x 60x 260x 3x 3x 3x 57x 57x 57x 8924x 8924x 8924x 8924x 76x 57x 57x 19x 19x 19x 3x 3x 16x 16x 16x 3x 3x 16x 19x 13x 13x 13x 8924x 8924x 267x 80x 80x 80x 39x 39x 41x 80x 18x 16x 16x 18x 18x 16x 16x 18x 80x 267x 267x 202x 202x 267x 8924x
import {Issue} from "../issue";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRegistry} from "../_iregistry";
import {Class, Interface} from "../objects";
import {IRule, IRuleMetadata} from "./_irule";
import {IObject} from "../objects/_iobject";
import {SyntaxLogic} from "../abap/5_syntax/syntax";
import {ISpaghettiScopeNode} from "../abap/5_syntax/_spaghetti_scope";
import {ReferenceType} from "../abap/5_syntax/_reference";
import {BuiltIn} from "../abap/5_syntax/_builtin";
import {ABAPObject} from "../objects/_abap_object";
 
export class CyclicOOConf extends BasicRuleConfig {
  /** List of object names to skip, must be full upper case name
   * @uniqueItems true
  */
  public skip: string[] = [];
  /** Skips shared memory enabled classes*/
  public skipSharedMemory: boolean = true;
}
 
export class CyclicOO implements IRule {
  private conf = new CyclicOOConf();
  private reg: IRegistry;
  private edges: { [from: string]: string[] } = {};
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "cyclic_oo",
      title: "Cyclic OO",
      shortDescription: `Finds cyclic OO references`,
      extendedInformation: `Runs for global INTF + CLAS objects`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: CyclicOOConf) {
    this.conf = conf;
    if (this.conf.skip === undefined) {
      this.conf.skip = [];
    }
  }
 
  public initialize(reg: IRegistry): IRule {
    this.reg = reg;
    this.edges = {};
    for (const obj of this.reg.getObjectsByType("CLAS")) {
      const name = obj.getName().toUpperCase();
      if (!(obj instanceof Class)) {
        continue;
      } else if (this.conf.skip.indexOf(name) >= 0) {
        continue;
      } else if (this.conf.skipSharedMemory === true && obj.getClassDefinition()?.isSharedMemory === true) {
        continue;
      }
      this.buildEdges(name, new SyntaxLogic(this.reg, obj).run().spaghetti.getTop());
    }
    for (const obj of this.reg.getObjectsByType("INTF")) {
      const name = obj.getName().toUpperCase();
      if (!(obj instanceof ABAPObject)) {
        continue;
      } else if (this.conf.skip.indexOf(name) >= 0) {
        continue;
      }
      this.buildEdges(name, new SyntaxLogic(this.reg, obj).run().spaghetti.getTop());
    }
    return this;
  }
 
  public run(obj: IObject): readonly Issue[] {
    if (!(obj instanceof Interface) && !(obj instanceof Class)) {
      return [];
    }
 
    const id = obj.getIdentifier();
    if (id === undefined) {
      return [];
    }
 
    const path = this.findCycle(obj.getName(), obj.getName(), [obj.getName()]);
    if (path) {
      const message = "Cyclic definition/usage: " + path;
      return [Issue.atIdentifier(id, message, this.getMetadata().key, this.conf.severity)];
    }
 
    return [];
  }
 
/////////////////////////////
 
  private findCycle(source: string, current: string, previous: readonly string[]): string | undefined {
    if (this.edges[current] === undefined) {
      return undefined;
    }
 
    for (const e of this.edges[current]) {
      if (e === source) {
        return previous.join(" -> ") + " -> " + source;
      }
      if (previous.indexOf(e) < 0) { // dont revisit vertices
        const found = this.findCycle(source, e, previous.concat([e]));
        if (found) {
          return found;
        }
      }
    }
 
    return undefined;
  }
 
  private buildEdges(from: string, node: ISpaghettiScopeNode): void {
    for (const r of node.getData().references) {
      if (r.resolved === undefined
          || node.getIdentifier().filename === r.resolved.getFilename()
          || r.resolved.getFilename() === BuiltIn.filename) {
        continue;
      }
      if (r.referenceType === ReferenceType.ObjectOrientedReference
          && r.extra?.ooName) {
        if (this.edges[from] === undefined) {
          this.edges[from] = [];
        }
        const name = r.extra.ooName.toUpperCase();
        if (name !== from && this.edges[from].indexOf(name) < 0) {
          this.edges[from].push(name);
        }
      }
    }
 
    for (const c of node.getChildren()) {
      this.buildEdges(from, c);
    }
  }
}