All files / src rules_runner.ts

87.6% Statements 106/121
57.89% Branches 22/38
100% Functions 4/4
87.6% Lines 106/121

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 1211x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 269x 269x 1x 1x 269x 269x 269x 269x 332x 1x 1x 331x 331x 269x 269x 1x 1x 269x 269x 269x 269x 269x 269x 269x 269x 269x 269x 331x 331x 268x 268x 331x 269x 269x 269x 43957x 43957x     43957x 43957x 43957x 269x 269x 269x 331x 331x 55241x 55241x 55241x 55241x 55241x 331x 269x 269x                       269x 269x 269x 1x 1x 269x 269x 269x 269x 269x 2x 2x 2x 2x 2x 2x 2x 269x 269x 269x 45999x 45999x 45999x 45998x 45998x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 269x 269x 269x 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";
 
export class RulesRunner {
  private readonly reg: IRegistry;
 
  public constructor(reg: IRegistry) {
    this.reg = reg;
  }
 
  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) {
        new SyntaxLogic(this.reg, obj).run();
      }
    }
 
    input?.progress?.set(rules.length, "Initialize Rules");
    for (const rule of rules) {
      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);
      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;
  }
}