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

95.12% Statements 195/205
84.61% Branches 88/104
100% Functions 3/3
95.12% Lines 195/205

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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 2051x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 510x 510x 510x 510x 510x 510x 510x 510x 510x         510x 510x 510x 510x 225x 225x 510x 510x 510x 1141x 1141x 498x 498x 643x 643x 1141x 433x 433x 433x 433x 433x 433x 433x 433x 91x 91x 88x 88x 433x 341x 1x 1x 1x 1x 341x 1x 1x 1x 1x 341x 341x 2x 2x 2x 2x 341x 341x 341x 341x 433x 8x 8x 8x 8x 8x 8x 428x 433x 2x 433x 7x 7x 7x 426x 377x 377x 377x 421x 421x 433x 377x 433x 42x 42x 1141x   210x 1x 1x 631x 631x 631x 498x 498x 498x 1x 1x 1x 1x 1x 291x 291x 291x 291x 291x 291x 291x 270x 291x       21x 21x 291x 291x 5x 5x 16x 16x 16x 16x 291x 291x 20x 20x 8x 8x 12x 20x 20x 8x 291x 6x 6x 6x 6x 6x 6x 6x 6x     2x 2x 2x 1x 1x 510x 136x 136x 136x 136x 22x 22x 22x 136x 3x 3x 3x 3x 111x 111x 510x 55x 374x 84x 319x 10x 235x 225x 225x 138x 138x 225x 87x 87x 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";
import {Visibility} from "../../4_file_information/visibility";
import {IMethodDefinition} from "../../types/_method_definition";
import {IClassDefinition} from "../../types/_class_definition";
import {IInterfaceDefinition} from "../../types/_interface_definition";
import {ClassDefinition} from "../../types/class_definition";
 
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 notVisible = method ? this.checkVisibility(method, def, foundDef, input) : 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 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;
  }
 
//////////////////////////////////////
 
  // returns the visibility as text if the method cannot be accessed from the current scope
  private static checkVisibility(
    method: IMethodDefinition,
    def: IClassDefinition | IInterfaceDefinition | undefined,
    foundDef: IClassDefinition | IInterfaceDefinition | undefined,
    input: SyntaxInput): string | undefined {
 
    const visibility = method.getVisibility();
    if (visibility === Visibility.Public || foundDef === undefined) {
      return undefined;
    } else if (!(foundDef instanceof ClassDefinition)) {
      // interface members are always public
      return undefined;
    }
 
    const name = foundDef.getName().toUpperCase();
    const enclosing = input.scope.getEnclosingClassName()?.toUpperCase();
    if (enclosing === undefined || enclosing === name) {
      return undefined;
    }
 
    // friendship with the class used at the call site also gives access to inherited members
    const helper = new ObjectOriented(input.scope);
    const visited: string[] = [];
    let current: IClassDefinition | undefined = def instanceof ClassDefinition ? def : foundDef;
    while (current !== undefined && visited.includes(current.getName().toUpperCase()) === false) {
      visited.push(current.getName().toUpperCase());
      if (helper.hasFriendship(current, enclosing)) {
        return undefined;
      }
      const sup: string | undefined = current.getSuperClass();
      current = sup === undefined ? undefined : input.scope.findClassDefinition(sup);
    }
 
    if (visibility === Visibility.Protected) {
      // subclasses can access protected members
      let sup = input.scope.findClassDefinition(enclosing)?.getSuperClass();
      while (sup !== undefined) {
        if (sup.toUpperCase() === name) {
          return undefined;
        }
        sup = input.scope.findClassDefinition(sup)?.getSuperClass();
      }
      return "protected";
    }
 
    return "private";
  }
 
  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;
  }
 
}