All files / src/abap cross_include_macros.ts

97.93% Statements 95/97
83.33% Branches 20/24
100% Functions 5/5
97.93% Lines 95/97

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 981x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10339x 10339x 1x 1x 10339x 10339x 10335x 10335x 4x 4x 4x 10339x 10339x 10339x 5x 5x     5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 4x 5x 4x 1x 1x 1x 1x 10339x 10339x 10822x 10788x 10788x 34x 34x 5x 5x 5x 34x 34x 10339x 10339x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 5x 5x 8x 5x 5x 4x 4x 5x 8x 1x 1x 1x 1x 1x  
import {IRegistry} from "../_iregistry";
import {ABAPObject} from "../objects/_abap_object";
import {Program} from "../objects/program";
import {Unknown} from "./2_statements/statements/_statement";
import {ExpandMacros} from "./2_statements/expand_macros";
import {IncludeGraph} from "../utils/include_graph";
 
// Macros can be defined in one include and used in a sibling include, connected
// via the main program. Objects are parsed in isolation, so the sibling include
// initially ends up with Unknown statements. This second pass finds the main
// program(s) for such includes, collects the macro definitions visible there,
// and re-parses the include with the macro names as extra global macros.
export class CrossIncludeMacros {
  private readonly reg: IRegistry;
 
  public constructor(reg: IRegistry) {
    this.reg = reg;
  }
 
  public run(): void {
    const candidates = this.findCandidates();
    if (candidates.length === 0) {
      return;
    }
 
    const graph = new IncludeGraph(this.reg);
    const config = this.reg.getConfig();
    const globalMacros = config.getSyntaxSetttings().globalMacros || [];
 
    for (const prog of candidates) {
      const mains = this.findMains(graph, prog);
      if (mains.size === 0) {
        continue;
      }
 
      const expand = new ExpandMacros(globalMacros, config.getRelease(), this.reg, config.getLanguageVersion());
      for (const main of mains) {
        for (const file of main.getABAPFiles()) {
          // find() follows INCLUDE statements, so this collects macros from the full include chain
          expand.find([...file.getStatements()], file, false);
        }
      }
 
      const macroNames = expand.listMacroNames();
      if (this.matchesUnknown(prog, new Set(macroNames))) {
        prog.setDirty();
        prog.parse(config.getRelease(), macroNames, this.reg, config.getLanguageVersion());
      }
    }
  }
 
//////////////////////////////
 
  private findCandidates(): Program[] {
    const ret: Program[] = [];
    for (const obj of this.reg.getObjects()) {
      if (!(obj instanceof Program) || obj.isInclude() === false) {
        continue;
      }
      for (const file of obj.getABAPFiles()) {
        if (file.getStatements().some(s => s.get() instanceof Unknown)) {
          ret.push(obj);
          break;
        }
      }
    }
    return ret;
  }
 
  private findMains(graph: IncludeGraph, prog: Program): Set<ABAPObject> {
    const ret = new Set<ABAPObject>();
    const filename = prog.getMainABAPFile()?.getFilename();
    for (const mainFilename of graph.listMainForInclude(filename)) {
      const file = this.reg.getFileByName(mainFilename);
      const obj = file ? this.reg.findObjectForFile(file) : undefined;
      if (obj instanceof ABAPObject) {
        ret.add(obj);
      }
    }
    return ret;
  }
 
  private matchesUnknown(prog: Program, macroNames: Set<string>): boolean {
    for (const file of prog.getABAPFiles()) {
      for (const s of file.getStatements()) {
        if (s.get() instanceof Unknown) {
          const name = ExpandMacros.findName(s.getTokens());
          if (name !== undefined && macroNames.has(name.toUpperCase())) {
            return true;
          }
        }
      }
    }
    return false;
  }
 
}