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

97.25% Statements 177/182
92% Branches 92/100
100% Functions 3/3
97.25% Lines 177/182

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 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 1831x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2178x 2178x 2178x 2178x 2178x 2178x 2178x 2178x 2178x         2178x 2178x 2178x 2178x 1064x 1064x 2178x 2178x 2178x 4343x 4343x 2155x 2155x 2188x 2188x 4343x 1624x 1624x 1624x 1624x 1624x 1624x 1624x 1624x 1624x 1624x 5x 5x 5x 1619x 1619x 1624x 223x 223x 220x 220x 1624x 1395x 1x 1x 1x 1x 1395x 1x 1x 1x 1x 1393x 1395x 1395x 1395x 2x 2x 2x 2x 1395x 1395x 5x 5x 5x 1395x 1395x 1395x 1395x 1624x 15x 15x 15x 15x 15x 15x 1609x 1624x 2x 1624x 7x 7x 7x 1607x 1556x 1556x 1556x 1602x 1602x 1624x 1556x 1556x 1556x 1556x 15x 15x 1624x 44x 44x 4343x   564x 2x 2x 2165x 2165x 2165x 2155x 2155x 2155x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 16x 1x 1x 2178x 236x 236x 236x 236x 24x 24x 24x 236x 3x 3x 3x 3x 209x 209x 2178x 278x 1942x 344x 1664x 256x 1320x 1064x 1064x 915x 915x 1064x 149x 149x 1x 1x  
import {ExpressionNode, TokenNode} from "../../nodes";
import * as Expressions from "../../2_statements/expressions";
import {AbstractType} from "../../types/basic/_abstract_type";
import {VoidType, ObjectReferenceType, IntegerType, Integer8Type, PackedType, FloatType, FloatingPointType, DecFloatType, DecFloat16Type, DecFloat34Type} 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, BuiltInMethod} 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) {
          const argumentType = MethodCallParam.runSyntax(param, input, method);
          if (method instanceof BuiltInMethod
              && BuiltIn.returnsArgumentType(methodName)
              && this.isNumeric(argumentType)) {
            context = argumentType;
          }
        } 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, ReferenceType.DataReadReference);
      }
 
      previous = current;
    }
 
    return context;
  }
 
//////////////////////////////////////
 
  private static isNumeric(type: AbstractType | undefined): boolean {
    return type instanceof IntegerType
      || type instanceof Integer8Type
      || type instanceof PackedType
      || type instanceof FloatType
      || type instanceof FloatingPointType
      || type instanceof DecFloatType
      || type instanceof DecFloat16Type
      || type instanceof DecFloat34Type;
  }
 
  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;
  }
 
}