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

96.98% Statements 161/166
91.01% Branches 81/89
100% Functions 2/2
96.98% Lines 161/166

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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 1661x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 628x 628x 628x 628x 628x 628x 628x 628x 628x         628x 628x 628x 628x 293x 293x 628x 628x 628x 1402x 1402x 605x 605x 797x 797x 1402x 544x 544x 544x 544x 544x 544x 544x 544x 544x 544x 5x 5x 5x 539x 539x 544x 136x 136x 133x 133x 544x 402x 1x 1x 1x 1x 402x 1x 1x 1x 1x 400x 402x 402x 402x 2x 2x 2x 2x 402x 402x 5x 5x 5x 402x 402x 402x 402x 544x 15x 15x 15x 15x 15x 15x 529x 544x 2x 544x 7x 7x 7x 527x 477x 477x 477x 522x 522x 544x 477x 544x 43x 43x 1402x   253x 1x 1x 774x 774x 774x 605x 605x 605x 1x 1x 1x 1x 628x 152x 152x 152x 152x 23x 23x 23x 152x 3x 3x 3x 3x 126x 126x 628x 81x 476x 91x 395x 11x 304x 293x 293x 168x 168x 293x 125x 125x 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);
        const isSuper = current === children[2]
          && first.get() instanceof Expressions.FieldChain
          && first.concatTokens().toUpperCase() === "SUPER";
        const superMessage = isSuper ? helper.checkSuperCall(def, methodName) : undefined;
        if (superMessage !== undefined) {
          input.issues.push(syntaxIssue(input, methodToken!, superMessage));
          return VoidType.get(CheckSyntaxKey);
        }
        // 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 notVisible = method
            ? helper.memberNotVisible(method.getVisibility(), def, foundDef, input.scope.getEnclosingClassName())
            : undefined;
          if (notVisible !== undefined) {
            const message = `Method "${methodName}" is ${notVisible} and cannot be accessed`;
            input.issues.push(syntaxIssue(input, methodToken!, message));
            return VoidType.get(CheckSyntaxKey);
          }
          const specialMessage = isSuper ? undefined : helper.checkSpecialTestMethodCall(method, foundDef);
          if (specialMessage !== undefined) {
            input.issues.push(syntaxIssue(input, methodToken!, specialMessage));
            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;
  }
 
}