All files / src/rules cyclic_oo.ts

91.66% Statements 143/156
84% Branches 42/50
100% Functions 9/9
91.66% Lines 143/156

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 1561x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 18723x 18723x 18723x 18723x 18723x 18723x 18723x 18723x 1x 9363x 9363x 9363x 9363x 9363x 9363x 27925x 27925x 27925x 27925x 27925x 27925x 27925x 27925x 27925x 9363x 9363x 8898x 8898x 9363x 9363x 216x 216x     216x 9363x 9363x 219x 219x 219x 47x     47x 47x   47x   47x     47x 47x 2x 2x 45x 45x 219x 22x     22x 22x   22x     22x 22x 1x 1x 21x 21x 219x 219x 9363x 9363x 278x 211x 211x 67x 67x 278x 3x 3x 64x 64x 64x 64x 278x 3x 3x 3x 61x 61x 61x 9363x 9363x 9363x 9363x 82x 61x 61x 21x 21x 21x 3x 3x 18x 18x 18x 18x 3x 3x 18x 21x 15x 15x 15x 9363x 9363x 275x 84x 84x 84x 38x 38x 46x 84x 21x 18x 18x 21x 21x 18x 18x 21x 84x 275x 275x 209x 209x 275x 9363x
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
 
Objects must be without syntax errors for this rule to take effect`,
    };
  }
 
  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")) {
      if (this.reg.isDependency(obj)) {
        continue;
      }
      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;
      }
      const run = new SyntaxLogic(this.reg, obj).run();
      if (run.issues.length > 0) {
        continue;
      }
      this.buildEdges(name, run.spaghetti.getTop());
    }
    for (const obj of this.reg.getObjectsByType("INTF")) {
      if (this.reg.isDependency(obj)) {
        continue;
      }
      const name = obj.getName().toUpperCase();
      if (!(obj instanceof ABAPObject)) {
        continue;
      } else if (this.conf.skip.indexOf(name) >= 0) {
        continue;
      }
      const run = new SyntaxLogic(this.reg, obj).run();
      if (run.issues.length > 0) {
        continue;
      }
      this.buildEdges(name, 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 previous: { [key: string]: boolean } = {};
    previous[obj.getName()] = true;
    const path = this.findCycle(obj.getName(), obj.getName(), previous);
    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: { [key: string]: boolean }): string | undefined {
    if (this.edges[current] === undefined) {
      return undefined;
    }
 
    for (const e of this.edges[current]) {
      if (e === source) {
        return Object.keys(previous).join(" -> ") + " -> " + source;
      }
      if (previous[e] === undefined) { // dont revisit vertices
        previous[e] = true;
        const found = this.findCycle(source, e, previous);
        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);
    }
  }
}