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

96.55% Statements 140/145
89.33% Branches 67/75
100% Functions 2/2
96.55% Lines 140/145

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 1451x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 487x 487x 487x 487x 487x 487x 487x 487x 487x         487x 487x 487x 487x 222x 222x 487x 487x 487x 1077x 1077x 477x 477x 600x 600x 1077x 410x 410x 410x 410x 410x 410x 410x 410x 90x 90x 87x 87x 410x 319x 1x 1x 1x 1x 319x 1x 1x 1x 1x 319x 319x 319x 319x 410x 8x 8x 8x 8x 8x 8x 407x 410x 2x 410x 7x 7x 7x 405x 356x 356x 356x 400x 400x 410x 356x 410x 42x 42x 1077x   190x 1x 1x 590x 590x 590x 477x 477x 477x 1x 1x 1x 1x 487x 122x 122x 122x 122x 22x 22x 22x 122x 3x 3x 3x 3x 97x 97x 487x 49x 365x 84x 316x 10x 232x 222x 222x 136x 136x 222x 86x 86x 1x 1x
import {ExpressionNode, TokenNode} from "../../nodes";
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 {CheckSyntaxKey, SyntaxInput, syntaxIssue} from "../_syntax_input";
 
export class MethodCallChain {
  public static runSyntax(
    node: ExpressionNode,
    input: SyntaxInput,
    targetType?: AbstractType): AbstractType | undefined {
 
    const helper = new ObjectOriented(input.scope);
    const children = node.getChildren();
 
    const first = children[0];
    if (first === undefined) {
      const message = "MethodCallChain, first child expected";
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return VoidType.get(CheckSyntaxKey);
    }
 
    let currentIndex = 1;
    let context: AbstractType | undefined = this.findTop(first, input, targetType);
    if (first.get() instanceof Expressions.MethodCall) {
      currentIndex--;
    }
 
    let previous: ExpressionNode | TokenNode | undefined = undefined;
    while (currentIndex <= children.length) {
      const current = children[currentIndex];
      if (current === undefined) {
        break;
      }
      currentIndex++;
 
      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 = input.scope.findObjectDefinition(className);
        // eslint-disable-next-line prefer-const
        let {method, def: foundDef} = helper.searchMethodName(def, methodName);
        if (method === undefined && current === first) {
          method = BuiltIn.searchBuiltin(methodName?.toUpperCase());
          if (method) {
            input.scope.addReference(methodToken, method, ReferenceType.BuiltinMethodReference, input.filename);
          }
        } else {
          if (previous && previous.getFirstToken().getStr() === "=>" && method?.isStatic() === false) {
            const message = "Method \"" + methodName + "\" not static";
            input.issues.push(syntaxIssue(input, methodToken!, message));
            return VoidType.get(CheckSyntaxKey);
          }
          if (current === first && method?.isStatic() === false && input.scope.isInStaticMethod() === true) {
            const message = "Method \"" + methodName + "\" not static";
            input.issues.push(syntaxIssue(input, methodToken!, message));
            return VoidType.get(CheckSyntaxKey);
          }
          const voidedName = context instanceof VoidType ? context.getVoided() : undefined;
          const extra = helper.methodReferenceExtras(foundDef, className || voidedName);
          input.scope.addReference(methodToken, method, ReferenceType.MethodReference, input.filename, extra);
        }
        if (methodName?.includes("~")) {
          const name = methodName.split("~")[0];
          const idef = input.scope.findInterfaceDefinition(name);
          if (idef) {
            input.scope.addReference(methodToken, idef, ReferenceType.ObjectOrientedReference, input.filename);
          }
        }
 
        if (method === undefined && methodName?.toUpperCase() === "CONSTRUCTOR") {
          context = undefined; // todo, this is a workaround, constructors always exists
        } else if (method === undefined && !(context instanceof VoidType)) {
          const message = "Method \"" + methodName + "\" not found, methodCallChain";
          input.issues.push(syntaxIssue(input, methodToken!, message));
          return VoidType.get(CheckSyntaxKey);
        } else if (method) {
          const ret = method.getParameters().getReturning()?.getType();
          context = ret;
        }
 
        const param = current.findDirectExpression(Expressions.MethodCallParam);
        if (param && method) {
          MethodCallParam.runSyntax(param, input, method);
        } else if (param && context instanceof VoidType) {
          MethodCallParam.runSyntax(param, input, context);
        }
      } else if (current instanceof ExpressionNode && current.get() instanceof Expressions.ComponentName) {
        context = ComponentName.runSyntax(context, current, input);
      } else if (current instanceof ExpressionNode && current.get() instanceof Expressions.AttributeName) {
        context = AttributeName.runSyntax(context, current, input);
      }
 
      previous = current;
    }
 
    return context;
  }
 
//////////////////////////////////////
 
  private static findTop(first: INode, input: SyntaxInput, targetType: AbstractType | undefined): AbstractType | undefined {
    if (first.get() instanceof Expressions.ClassName) {
      const token = first.getFirstToken();
      const className = token.getStr();
      const classDefinition = input.scope.findObjectDefinition(className);
      if (classDefinition === undefined && input.scope.getDDIC().inErrorNamespace(className) === false) {
        const extra: IReferenceExtras = {ooName: className, ooType: "Void"};
        input.scope.addReference(token, undefined, ReferenceType.ObjectOrientedVoidReference, input.filename, extra);
        return VoidType.get(className);
      } else if (classDefinition === undefined) {
        const message = "Class " + className + " not found";
        input.issues.push(syntaxIssue(input, first.getFirstToken(), message));
        return VoidType.get(CheckSyntaxKey);
      }
      input.scope.addReference(first.getFirstToken(), classDefinition, ReferenceType.ObjectOrientedReference, input.filename);
      return new ObjectReferenceType(classDefinition);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.FieldChain) {
      return FieldChain.runSyntax(first, input, ReferenceType.DataReadReference);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.NewObject) {
      return NewObject.runSyntax(first, input, targetType);
    } else if (first instanceof ExpressionNode && first.get() instanceof Expressions.Cast) {
      return Cast.runSyntax(first, input, targetType);
    } else {
      const meType = input.scope.findVariable("me")?.getType();
      if (meType) {
        return meType;
      }
    }
    return undefined;
  }
 
}