All files / src/abap/5_syntax/statements create_object.ts

88.51% Statements 185/209
83.33% Branches 90/108
100% Functions 3/3
88.51% Lines 185/209

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 205 206 207 208 2091x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 64x 64x 64x 64x 64x 64x 16x 16x 16x 16x 13x 13x 13x 1x 1x 1x 1x 16x   3x 3x 3x 3x 3x 16x 60x 60x 64x     60x 64x     60x 60x 60x 60x 60x 60x 10x 60x 5x 5x 5x 50x 45x 45x 45x       45x 2x 2x 2x 45x 35x 35x 35x 2x 2x 2x 35x 33x 33x       33x 26x 26x 35x 2x 2x 2x 2x 35x 49x 60x 1x 1x 60x 49x 64x 5x 5x 49x 64x 64x 8x 8x 49x 64x 64x 64x 64x 36x 36x 1x 1x 1x 36x 48x 48x 48x 1x 1x 84x 84x 78x 78x 6x 84x 1x 1x 1x 84x 1x 1x 4x 84x 3x 3x 1x 84x 84x 1x 1x 1x 1x 1x 1x                       84x 84x 1x 1x 48x 13x 13x 2x 2x 13x 13x 35x 35x 48x 48x 48x 48x 48x 48x 11x 11x     11x 11x 11x 11x 11x 11x 3x 3x 3x 11x 1x 1x 1x 1x 7x 7x 7x 31x 48x 2x 2x 2x 2x 29x 1x
import * as Expressions from "../../2_statements/expressions";
import {StatementNode} from "../../nodes";
import {Source} from "../expressions/source";
import {Target} from "../expressions/target";
import {Dynamic} from "../expressions/dynamic";
import {ReferenceType} from "../_reference";
import {AnyType, DataType, GenericObjectReferenceType, ObjectReferenceType, UnknownType, VoidType} from "../../types/basic";
import {ClassDefinition, InterfaceDefinition} from "../../types";
import {StatementSyntax} from "../_statement_syntax";
import {IClassDefinition} from "../../types/_class_definition";
import {ObjectOriented} from "../_object_oriented";
import {TypeUtils} from "../_type_utils";
import {SyntaxInput, syntaxIssue} from "../_syntax_input";
import {Visibility} from "../../4_file_information/visibility";
 
export class CreateObject implements StatementSyntax {
  public runSyntax(node: StatementNode, input: SyntaxInput): void {
 
    let cdef: IClassDefinition | undefined = undefined;
 
    // CREATE OBJECT, TYPE
    const type = node.findExpressionAfterToken("TYPE");
    if (type && type.get() instanceof Expressions.ClassName) {
      const token = type.getFirstToken();
      const name = token.getStr();
      cdef = input.scope.findClassDefinition(name);
      if (cdef) {
        input.scope.addReference(token, cdef, ReferenceType.ObjectOrientedReference, input.filename);
        input.scope.addReference(token, cdef, ReferenceType.ConstructorReference, input.filename, {ooName: cdef.getName()});
        if (cdef.isAbstract() === true) {
          const message = cdef.getName() + " is abstract, cannot be instantiated";
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return;
        }
      } else if (input.scope.getDDIC().inErrorNamespace(name) === false) {
        input.scope.addReference(token, undefined, ReferenceType.ObjectOrientedVoidReference, input.filename, {ooName: name, ooType: "CLAS"});
      } else {
        const message = "TYPE \"" + name + "\" not found";
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      }
    }
 
    // just recurse
    for (const s of node.findDirectExpressions(Expressions.Source)) {
      Source.runSyntax(s, input);
    }
 
    for (const t of node.findDirectExpression(Expressions.ParameterListExceptions)?.findAllExpressions(Expressions.Target) || []) {
      Target.runSyntax(t, input);
    }
 
    const t = node.findDirectExpression(Expressions.Target);
    let found = undefined;
    if (t) {
      found = Target.runSyntax(t, input);
      if (found instanceof VoidType) {
        // do nothing
      } else if (found instanceof UnknownType) {
        const message = "Target type unknown, " + t.concatTokens();
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      } else if (!(found instanceof ObjectReferenceType)
          && !(found instanceof AnyType)
          && !(found instanceof DataType)
          && !(found instanceof GenericObjectReferenceType)) {
        const message = "Target must be an object reference, " + t.concatTokens();
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      } else if (found instanceof GenericObjectReferenceType && type === undefined) {
        const message = "Generic type, cannot be instantiated";
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      } else if (found instanceof ObjectReferenceType) {
        const identifier = found.getIdentifier();
        const idFound = input.scope.findObjectDefinition(identifier.getName());
        if (idFound instanceof InterfaceDefinition && type === undefined) {
          const message = "Interface reference, cannot be instantiated";
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return;
        } else if (found instanceof ObjectReferenceType
            && type === undefined
            && input.scope.findInterfaceDefinition(found.getQualifiedName())) {
          const message = "Interface reference, cannot be instantiated";
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return;
        } else if (idFound instanceof ClassDefinition && cdef === undefined) {
          cdef = idFound;
        }
        if (type === undefined && idFound instanceof ClassDefinition && idFound.isAbstract() === true) {
          const message = identifier.getName() + " is abstract, cannot be instantiated";
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return;
        }
      }
 
      if (found instanceof ObjectReferenceType && cdef === undefined) {
        cdef = input.scope.findClassDefinition(found.getQualifiedName());
      }
    }
 
    for (const t of node.findDirectExpressions(Expressions.Dynamic)) {
      Dynamic.runSyntax(t, input);
    }
 
    let ooName = cdef?.getName();
    if (ooName === undefined && found instanceof VoidType) {
      ooName = found.getVoided();
    }
 
    input.scope.addReference(t?.getFirstToken(), cdef, ReferenceType.ConstructorReference, input.filename,
                             {ooName: ooName});
 
    if (cdef !== undefined) {
      const err = CreateObject.checkInstantiationAllowed(cdef, input);
      if (err) {
        input.issues.push(syntaxIssue(input, node.getFirstToken(), err));
        return;
      }
    }
 
    this.validateParameters(cdef, node, input);
  }
 
  public static checkInstantiationAllowed(cdef: IClassDefinition, input: SyntaxInput): string | undefined {
    const createVis = cdef.getCreateVisibility();
    if (createVis === Visibility.Public) {
      return undefined;
    }
    const enclosingClass = input.scope.getEnclosingClassName();
    if (enclosingClass === undefined) {
      return cdef.getName() + " cannot be instantiated, class is defined as " +
        (createVis === Visibility.Private ? "private" : "protected");
    }
    if (enclosingClass.toUpperCase() === cdef.getName().toUpperCase()) {
      return undefined;
    }
    if (cdef.getFriends().some(f => f.toUpperCase() === enclosingClass.toUpperCase()) ||
        input.scope.isLocalFriend(cdef.getName(), enclosingClass)) {
      return undefined;
    }
    // subclasses of friends also have friendship
    let enclosingSup = input.scope.findClassDefinition(enclosingClass)?.getSuperClass();
    while (enclosingSup !== undefined) {
      if (cdef.getFriends().some(f => f.toUpperCase() === enclosingSup!.toUpperCase()) ||
          input.scope.isLocalFriend(cdef.getName(), enclosingSup)) {
        return undefined;
      }
      enclosingSup = input.scope.findClassDefinition(enclosingSup)?.getSuperClass();
    }
    if (createVis === Visibility.Protected) {
      // subclasses are also allowed
      let sup = input.scope.findClassDefinition(enclosingClass)?.getSuperClass();
      while (sup !== undefined) {
        if (sup.toUpperCase() === cdef.getName().toUpperCase()) {
          return undefined;
        }
        sup = input.scope.findClassDefinition(sup)?.getSuperClass();
      }
    }
    return cdef.getName() + " cannot be instantiated, class is defined as " +
      (createVis === Visibility.Private ? "private" : "protected");
  }
 
  private validateParameters(cdef: IClassDefinition | undefined, node: StatementNode, input: SyntaxInput): void {
    if (cdef === undefined) {
      const sources = node.findDirectExpression(Expressions.ParameterListS)?.findAllExpressions(Expressions.Source);
      for (const s of sources || []) {
        Source.runSyntax(s, input);
      }
      return;
    }
 
    const methodDef = new ObjectOriented(input.scope).searchMethodName(cdef, "CONSTRUCTOR");
    const methodParameters = methodDef.method?.getParameters();
 
    const allImporting = methodParameters?.getImporting() || [];
    const requiredImporting = new Set(methodParameters?.getRequiredParameters().map(i => i.getName().toUpperCase()));
 
    for (const p of node.findDirectExpression(Expressions.ParameterListS)?.findAllExpressions(Expressions.ParameterS) || []) {
      const name = p.findDirectExpression(Expressions.ParameterName)?.concatTokens().toUpperCase();
      if (name === undefined) {
        continue;
      }
 
      const source = p.findDirectExpression(Expressions.Source);
      const sourceType = Source.runSyntax(source, input);
 
      const found = allImporting?.find(p => p.getName().toUpperCase() === name);
      if (found === undefined) {
        const message = `constructor parameter "${name}" does not exist`;
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      } else if (new TypeUtils(input.scope).isAssignableStrict(sourceType, found.getType(), source) === false) {
        const message = `constructor parameter "${name}" type not compatible`;
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      }
 
      requiredImporting.delete(name);
    }
 
    for (const r of requiredImporting.values()) {
      const message = `constructor parameter "${r}" must be supplied`;
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return;
    }
  }
}