All files / src/rules cyclic_oo.ts

92.31% Statements 60/65
86.67% Branches 39/45
100% Functions 9/9
92.31% Lines 60/65

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 1331x 1x   1x     1x   1x 1x 1x   1x       14733x     1x 7368x   7368x     21954x                 7061x       138x 138x           141x 141x 141x 31x 31x   31x     31x   141x 13x 13x   13x     13x   141x       166x 124x     42x 42x 2x     40x 40x 3x 3x     37x           54x 37x     17x 17x 3x   14x 14x 14x 3x         11x       172x 59x     32x   27x   15x 13x   15x 15x 13x         172x 128x      
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[] = [];
}
 
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;
    Iif (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();
      Iif (!(obj instanceof ABAPObject)) {
        continue;
      } else Iif (this.conf.skip.indexOf(name) >= 0) {
        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();
      Iif (!(obj instanceof ABAPObject)) {
        continue;
      } else Iif (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;
      }
      Eif (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);
    }
  }
}