All files / src rules_runner.ts

74.11% Statements 126/170
48.93% Branches 23/47
71.42% Functions 5/7
74.11% Lines 126/170

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 164 165 166 167 168 169 1701x 1x 1x 1x 1x 1x 1x 1x 1x 251x 251x 251x 251x                   251x 251x                         251x 1x 1x 1x 1x 1x 1x 251x 251x 251x 1x 1x 251x 251x 251x 251x 322x 1x 1x 321x 321x 251x 251x 1x 1x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 321x 321x 250x 250x 250x     250x 321x 251x       251x 251x 251x 43747x 43747x   43747x 43747x 43747x 43747x     43747x 43747x 43747x 43747x     43747x 43747x 251x 251x 251x 321x 321x 57287x 57287x 57287x 57287x 57287x 321x 251x 251x                       251x 251x 251x 1x 1x 251x 251x 251x 251x 251x 2x 2x 2x 2x 2x 2x 2x 251x 251x 251x 45933x 45933x 45933x 45932x 45932x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 251x 251x 251x 1x
import {SyntaxLogic} from "./abap/5_syntax/syntax";
import {ArtifactsRules} from "./artifacts_rules";
import {Issue} from "./issue";
import {ABAPObject} from "./objects/_abap_object";
import {IObject} from "./objects/_iobject";
import {SkipLogic} from "./skip_logic";
import {ExcludeHelper} from "./utils/excludeHelper";
import {IRegistry, IRunInput} from "./_iregistry";
 
class SyntaxPerformance {
  private readonly results: {runtime: number, name: string}[] = [];
 
  public push(obj: IObject, runtime: number): void {
    if (runtime < 100) {
      return;
    }

    this.results.push({
      runtime: runtime,
      name: obj.getType() + " " + obj.getName(),
    });
  }
 
  public output() {
    const MAX = 10;

    this.results.sort((a, b) => { return b.runtime - a.runtime; });

    for (let i = 0; i < MAX; i++) {
      const row = this.results[i];
      if (row === undefined) {
        break;
      }
      process.stderr.write(`\t${row.runtime}ms\t${row.name}\n`);
    }
  }
}
 
export class RulesRunner {
  private readonly reg: IRegistry;
  private readonly syntaxPerformance: SyntaxPerformance;
 
  public constructor(reg: IRegistry) {
    this.reg = reg;
    this.syntaxPerformance = new SyntaxPerformance();
  }
 
  public objectsToCheck(objects: Iterable<IObject>): readonly IObject[] {
    const check: IObject[] = [];
    const skipLogic = new SkipLogic(this.reg);
 
    for (const obj of objects) {
      if (skipLogic.skip(obj) || this.reg.isDependency(obj)) {
        continue;
      }
      check.push(obj);
    }
    return check;
  }
 
  public runRules(objects: Iterable<IObject>, input?: IRunInput): readonly Issue[] {
    const rulePerformance: {[index: string]: number} = {};
    const issues = [];
 
    const rules = this.reg.getConfig().getEnabledRules();
    const check = this.objectsToCheck(objects);
 
    // note: SyntaxLogic is cached, logic is run as first step in order
    // not to penalize the performance of the first rule using SyntaxLogic information
    input?.progress?.set(check.length, "Run Syntax");
    for (const obj of check) {
      input?.progress?.tick("Run Syntax - " + obj.getName());
      if (obj instanceof ABAPObject) {
        const start = Date.now();
        new SyntaxLogic(this.reg, obj).run();
        if (input?.outputPerformance === true) {
          this.syntaxPerformance.push(obj, Date.now() - start);
        }
      }
    }
    if (input?.outputPerformance === true) {
      process.stderr.write("Syntax Performance:\n");
      this.syntaxPerformance.output();
    }
 
    input?.progress?.set(rules.length, "Initialize Rules");
    for (const rule of rules) {
      const start = Date.now();
      if (input?.outputPerformance === true) {
        process.stderr.write("Initializing rule " + rule.getMetadata().key);
      } else {
        input?.progress?.tick("Initialize Rules - " + rule.getMetadata().key);
      }
      if (rule.initialize === undefined) {
        throw new Error(rule.getMetadata().key + " missing initialize method");
      }
 
      rule.initialize(this.reg);
 
      if (input?.outputPerformance === true) {
        process.stderr.write(", " + (Date.now() - start) + "ms\n");
      }
      rulePerformance[rule.getMetadata().key] = 0;
    }
 
    input?.progress?.set(check.length, "Finding Issues");
    for (const obj of check) {
      input?.progress?.tick("Finding Issues - " + obj.getType() + " " + obj.getName());
      for (const rule of rules) {
        const before = Date.now();
        issues.push(...rule.run(obj));
        const runtime = Date.now() - before;
        rulePerformance[rule.getMetadata().key] = rulePerformance[rule.getMetadata().key] + runtime;
      }
    }
 
    if (input?.outputPerformance === true) {
      const perf: {name: string, time: number}[] = [];
      for (const p in rulePerformance) {
        if (rulePerformance[p] > 100) { // ignore rules if it takes less than 100ms
          perf.push({name: p, time: rulePerformance[p]});
        }
      }
      perf.sort((a, b) => {return b.time - a.time;});
      for (const p of perf) {
        process.stderr.write("\t" + p.time + "ms\t" + p.name + "\n");
      }
    }
 
    return this.excludeIssues(issues);
  }
 
  public excludeIssues(issues: Issue[]): Issue[] {
    const ret: Issue[] = issues;
 
    const globalNoIssues = this.reg.getConfig().getGlobal().noIssues || [];
    const globalNoIssuesPatterns = globalNoIssues.map(x => new RegExp(x, "i"));
    if (globalNoIssuesPatterns.length > 0) {
      for (let i = ret.length - 1; i >= 0; i--) {
        const filename = ret[i].getFilename();
        if (ExcludeHelper.isExcluded(filename, globalNoIssuesPatterns)) {
          ret.splice(i, 1);
        }
      }
    }
 
    // exclude issues, as now we know both the filename and issue key
    for (const rule of ArtifactsRules.getRules()) {
      const key = rule.getMetadata().key;
      const ruleExclude: string[] = this.reg.getConfig().readByKey(key, "exclude") ?? [];
      if (ruleExclude.length === 0) {
        continue;
      }
      const ruleExcludePatterns = ruleExclude.map(x => new RegExp(x, "i"));
 
      for (let i = ret.length - 1; i >= 0; i--) {
        if (ret[i].getKey() !== key) {
          continue;
        }
 
        const filename = ret[i].getFilename();
        if (ExcludeHelper.isExcluded(filename, ruleExcludePatterns)) {
          ret.splice(i, 1);
        }
      }
    }
 
    return ret;
  }
}