All files / src/abap/5_syntax/expressions method_call_chain.ts

96.24% Statements 128/133
88.73% Branches 63/71
100% Functions 2/2
96.24% Lines 128/133

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 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 383x 383x 383x 383x 383x 383x 383x 383x 383x 383x     383x 383x 383x 171x 171x 369x 369x 383x 463x 463x     463x 463x 316x 316x 316x 316x 316x 316x 316x 316x 79x 79x 75x 75x 316x 237x 1x 1x 236x 237x 237x 237x 237x 316x 8x 8x 8x 8x 8x 8x 315x 316x 2x 316x 7x 313x 276x 276x 276x 308x 308x 316x 276x 316x 30x 30x 463x   147x 1x 1x 400x 400x 400x 306x 306x 306x 1x 1x 1x 1x 383x 95x 95x 95x 95x 18x 18x 18x 95x 3x 3x 74x 74x 383x 39x 288x 69x 249x 9x 180x 171x 171x 95x 95x 171x 76x 76x 1x 1x
import {ExpressionNode, TokenNode} from "../../nodes";
import {CurrentScope} from "../_current_scope";
import * as Expressions from "../../2_statements/expressions";
import {AbstractType} from "../../types/basic/_abstract_type";
import {VoidType, ObjectReferenceType} from "../../types/basic";
import {FieldChain} from "./field_chain";
import {INode} from "../../nodes/_inode";
import {ObjectOriented} from "../_object_oriented";
import {NewObject} from "./new_object";
import {Cast} from "./cast";
import {BuiltIn} from "../_builtin";
import {MethodCallParam} from "./method_call_param";
import {IReferenceExtras, ReferenceType} from "../_reference";
import {ComponentName} from "./component_name";
import {AttributeName} from "./attribute_name";
import {ClassDefinition} from "../../types/class_definition";
 
export class MethodCallChain {
  public runSyntax(
    node: ExpressionNode,
    scope: CurrentScope,
    filename: string,
    targetType?: AbstractType): AbstractType | undefined {
 
    const helper = new ObjectOriented(scope);
    const children = node.getChildren().slice();
 
    const first = children.shift();
    if (first === undefined) {
      throw new Error("MethodCallChain, first child expected");
    }
 
    let context: AbstractType | undefined = this.findTop(first, scope, targetType, filename);
    if (first.get() instanceof Expressions.MethodCall) {
      children.unshift(first);
    }
 
    let previous: ExpressionNode | TokenNode | undefined = undefined;
    while (children.length > 0) {
      const current = children.shift();
      if (current === undefined) {
        break;
      }
 
      if (current instanceof ExpressionNode && current.get() instanceof Expressions.MethodCall) {
        // for built-in methods set className to undefined
        const className = context instanceof ObjectReferenceType ? context.getIdentifierName() : undefined;
        const methodToken = current.findDirectExpression(Expressions.MethodName)?.getFirstToken();
        const methodName = methodToken?.getStr();
        const def = scope.findObjectDefinition(className);
        // eslint-disable-next-line prefer-const
        let {method, def: foundDef} = helper.searchMethodName(def, methodName);
        if (method === undefined && current === first) {
          method = new BuiltIn().searchBuiltin(methodName?.toUpperCase());
          if (method) {
            scope.addReference(methodToken, method, ReferenceType.BuiltinMethodReference, filename);
          }
        } else {
          if (previous && previous.getFirstToken().getStr() === "=>" && method?.isStatic() === false) {
            throw new Error("Method \"" + methodName + "\" not static");
          }
          const extra: IReferenceExtras = {
            ooName: foundDef?.getName(),
            ooType: foundDef instanceof ClassDefinition ? "CLAS" : "INTF"};
          scope.addReference(methodToken, method, ReferenceType.MethodReference, filename, extra);
        }
        if (methodName?.includes("~")) {
          const name = methodName.split("~")[0];
          const idef = scope.findInterfaceDefinition(name);
          if (idef) {
            scope.addReference(methodToken, idef, ReferenceType.ObjectOrientedReference, filename);
          }
        }
 
        if (method === undefined && methodName?.toUpperCase() === "CONSTRUCTOR") {
          context = undefined; // todo, this is a workaround, constructors always exists
        } else if (method === undefined && !(context instanceof VoidType)) {
          throw new Error("Method \"" + methodName + "\" not found, methodCallChain");
        } else if (method) {
          const ret = method.getParameters().getReturning()?.getType();
          context = ret;
        }
 
        const param = current.findDirectExpression(Expressions.MethodCallParam);
        if (param && method) {
          new MethodCallParam().runSyntax(param, scope, method, filename);
        } else if (param && context instanceof VoidType) {
          new MethodCallParam().runSyntax(param, scope, context, filename);
        }
      } else if (current instanceof ExpressionNode && current.get() instanceof Expressions.ComponentName) {
        context = new ComponentName().runSyntax(context, current);
      } else if (current instanceof ExpressionNode && current.get() instanceof Expressions.AttributeName) {
        context = new AttributeName().runSyntax(context, current, scope, filename);
      }
 
      previous = current;
    }
 
    return context;
  }
 
//////////////////////////////////////
 
  private findTop(first: INode, scope: CurrentScope, targetType: AbstractType | undefined, filename: string): AbstractType | undefined {
    if (first.get() instanceof Expressions.ClassName) {
      const token = first.getFirstToken();
      const className = token.getStr();
      const classDefinition = scope.findObjectDefinition(className);
      if (classDefinition === undefined && scope.getDDIC().inErrorNamespace(className) === false) {
        const extra: IReferenceExtras = {ooName: className, ooType: "Void"};
        scope.addReference(token, undefined, ReferenceType.ObjectOrientedVoidReference, filename, extra);
        return new VoidType(className);
      } else if (classDefinition === undefined) {
        throw new Error("Class " + className + " not found");
      }
      scope.addReference(first.getFirstToken(), classDefinition, ReferenceType.ObjectOrientedReference, filename);
      return new ObjectReferenceType(classDefinition);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.FieldChain) {
      return new FieldChain().runSyntax(first, scope, filename, ReferenceType.DataReadReference);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.NewObject) {
      return new NewObject().runSyntax(first, scope, targetType, filename);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.Cast) {
      return new Cast().runSyntax(first, scope, targetType, filename);
    } else {
      const meType = scope.findVariable("me")?.getType();
      if (meType) {
        return meType;
      }
    }
    return undefined;
  }
 
}