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

87.93% Statements 175/199
86.45% Branches 83/96
100% Functions 3/3
87.93% Lines 175/199

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 2001x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 69x 69x 69x 69x 69x 69x 16x 16x 16x 16x 13x 13x 13x 1x 1x 1x 1x 16x   3x 3x 3x 3x 3x 16x 65x 65x 69x     65x 69x     65x 65x 65x 65x 65x 65x 10x 65x 5x 5x 5x 55x 50x 50x 50x       50x 2x 2x 2x 50x 40x 40x 40x 2x 2x 2x 40x 38x 38x       38x 31x 31x 40x 2x 2x 2x 2x 40x 54x 65x 1x 1x 65x 54x 69x 5x 5x 54x 69x 69x 8x 8x 54x 69x 69x 69x 69x 41x 41x 1x 1x 1x 41x 53x 53x 53x 1x 1x 89x 89x 82x 82x 7x 89x 1x 1x 1x 89x 1x 1x 5x 5x 5x                       89x 89x 1x 1x 53x 13x 13x 2x 2x 13x 13x 40x 40x 53x 53x 53x 53x 53x 53x 12x 12x     12x 12x 12x 12x 12x 12x 3x 3x 3x 12x 2x 2x 2x 2x 7x 7x 7x 35x 53x 2x 2x 2x 2x 33x 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 (new ObjectOriented(input.scope).hasFriendship(cdef, enclosingClass)) {
      return undefined;
    }
    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;
    }
  }
}