All files / src/objects behavior_definition.ts

93.96% Statements 109/116
71.42% Branches 20/28
100% Functions 12/12
93.96% Lines 109/116

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 1171x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 14x 58x 58x 14x 14x 2x 2x 2x 2x 2x 14x 14x 15x 15x 15x 14x 14x 34x 34x 14x 14x 29x 16x 16x 13x 13x 13x 13x 29x 29x 29x 29x 29x 29x 29x 14x 14x 13x 13x 13x 14x 14x 5x 5x 14x 14x 3x 3x 3x 14x 14x 14x 3x 3x 3x 2x 2x 3x 1x 1x 14x 14x 14x 14x 10x 10x 10x 10x     10x 10x 10x 10x 10x 10x 10x 10x 14x 14x 14x 3x 3x 3x 3x     3x 3x 3x 3x 3x 3x 3x       3x 3x 3x 14x  
import {ExpressionNode} from "../abap/nodes";
import {BDEFParser} from "../bdef/bdef_parser";
import {BDEFAlias, BDEFDefineBehavior, BDEFName} from "../bdef/expressions";
import {AbstractObject} from "./_abstract_object";
import {IParseResult} from "./_iobject";
 
export type ParsedBehaviorDefinition = {
  /** entities defined in the behavior definition, alias is undefined if not specified */
  entities: {name: string, alias: string | undefined}[];
  tree: ExpressionNode | undefined;
};
 
export class BehaviorDefinition extends AbstractObject {
  private parsedData: ParsedBehaviorDefinition | undefined = undefined;
 
  public getType(): string {
    return "BDEF";
  }
 
  public getAllowedNaming() {
    return { // todo, verify
      maxLength: 30,
      allowNamespace: true,
    };
  }
 
  public setDirty(): void {
    this.parsedData = undefined;
    super.setDirty();
  }
 
  public findSourceFile() {
    return this.getFiles().find(f => f.getFilename().endsWith(".asbdef"));
  }
 
  public parse(): IParseResult {
    if (this.isDirty() === false && this.parsedData !== undefined) {
      return {updated: false, runtime: 0};
    }
 
    const start = Date.now();
    const tree = new BDEFParser().parse(this.findSourceFile());
    this.parsedData = {
      entities: tree ? this.findEntities(tree) : this.findEntitiesFallback(),
      tree,
    };
 
    this.dirty = false;
    return {updated: true, runtime: Date.now() - start};
  }
 
  public getTree(): ExpressionNode | undefined {
    this.parse();
    return this.parsedData?.tree;
  }
 
  public hasParserError(): boolean {
    return this.findSourceFile() !== undefined && this.getTree() === undefined;
  }
 
  public listEntities(): readonly {name: string, alias: string | undefined}[] {
    this.parse();
    return this.parsedData!.entities;
  }
 
  /** finds the entity name for a given alias, undefined if the alias is not defined */
  public findEntityNameByAlias(alias: string): string | undefined {
    const upper = alias.toUpperCase();
    for (const entity of this.listEntities()) {
      if (entity.alias?.toUpperCase() === upper) {
        return entity.name;
      }
    }
    return undefined;
  }
 
/////////////////////////
 
  private findEntities(tree: ExpressionNode): ParsedBehaviorDefinition["entities"] {
    const ret: ParsedBehaviorDefinition["entities"] = [];
    for (const define of tree.findDirectExpressions(BDEFDefineBehavior)) {
      const name = define.findDirectExpression(BDEFName);
      if (name === undefined) {
        continue;
      }
      const alias = define.findDirectExpression(BDEFAlias)?.findDirectExpression(BDEFName);
      ret.push({
        name: name.concatTokens().replace(/ /g, ""),
        alias: alias?.concatTokens().replace(/ /g, ""),
      });
    }
    return ret;
  }
 
  /** used if the source cannot be parsed */
  private findEntitiesFallback(): ParsedBehaviorDefinition["entities"] {
    const ret: ParsedBehaviorDefinition["entities"] = [];
 
    const raw = this.findSourceFile()?.getRaw();
    if (raw === undefined) {
      return ret;
    }
 
    // BDEF uses "//" for line comments
    const stripped = raw.replace(/\/\/.*$/gm, "");
    // eg. "define behavior for ZI_Booking alias Booking", also "define abstract behavior for ..."
    const regex = /\bdefine\s+(?:\w+\s+)*?behavior\s+for\s+([\w/]+)(?:\s+alias\s+(\w+))?/gi;
    let match = regex.exec(stripped);
    while (match !== null) {
      ret.push({name: match[1], alias: match[2]});
      match = regex.exec(stripped);
    }
 
    return ret;
  }
}