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

96.15% Statements 150/156
92.75% Branches 64/69
100% Functions 3/3
96.15% Lines 150/156

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 1571x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 211x 211x 211x 211x 211x 58x 58x 153x 153x 153x 211x 9x 9x 153x 153x 211x 36x 36x 35x 35x 36x 152x 152x 152x 211x 260x 4x 260x 66x 66x 1x 1x 1x 1x 65x 65x 65x 256x 119x 119x 103x 103x 1x 1x 1x 1x 102x 102x 102x 103x 118x 260x 150x 211x 63x 63x 150x 150x 211x 7x 7x 211x 20x 20x 150x 211x 117x 117x 117x 117x 117x         117x 117x 97x 97x 117x 117x 4x 5x 5x 5x 4x 117x 100x 100x 117x 53x 53x 3x 3x 53x 4x 4x 4x 4x 4x 4x 4x 2x 2x 2x 2x 4x 53x 117x 150x 211x 9x 9x 150x 211x 35x 35x 150x 211x 1x 1x 211x 211x 1x 1x 1x 1x 1x 50x 8x 8x 42x 50x 50x 1x 1x 8x 4x 4x 4x 4x     1x  
import {ExpressionNode} from "../../nodes";
import * as Expressions from "../../2_statements/expressions";
import {For} from "./for";
import {Source} from "./source";
import {AbstractType} from "../../types/basic/_abstract_type";
import {Let} from "./let";
import {FieldAssignment} from "./field_assignment";
import {AnyType, CharacterType, HexType, StringType, TableAccessType, TableType, UnknownType, VoidType} from "../../types/basic";
import {CheckSyntaxKey, SyntaxInput, syntaxIssue} from "../_syntax_input";
 
export class ValueBody {
  public static runSyntax(
    node: ExpressionNode | undefined,
    input: SyntaxInput,
    targetType: AbstractType | undefined): AbstractType | undefined {
 
    if (node === undefined) {
      return targetType;
    }
 
    let letScoped = false;
    const letNode = node.findDirectExpression(Expressions.Let);
    if (letNode) {
      letScoped = Let.runSyntax(letNode, input);
    }
 
    let forScopes = 0;
    for (const forNode of node.findDirectExpressions(Expressions.For) || []) {
      const scoped = For.runSyntax(forNode, input);
      if (scoped === true) {
        forScopes++;
      }
    }
 
    const fields = new Set<string>();
    const hasTableLines = node.findDirectExpression(Expressions.ValueBodyLine) !== undefined;
    for (const child of node.getChildren()) {
      if (!(child instanceof ExpressionNode)) {
        continue;
      } else if (child.get() instanceof Expressions.FieldAssignment) {
        const fieldname = child.findDirectExpression(Expressions.FieldSub)?.concatTokens().toUpperCase();
        if (fieldname && hasTableLines === false && fields.has(fieldname)) {
          const message = "Duplicate field assignment";
          input.issues.push(syntaxIssue(input, child.getFirstToken(), message));
          return VoidType.get(CheckSyntaxKey);
        }
        if (fieldname) {
          fields.add(fieldname);
        }
      } else if (child.get() instanceof Expressions.ValueBodyLine) {
        const rowFields = new Set(fields);
        for (const assignment of child.findDirectExpressions(Expressions.FieldAssignment)) {
          const fieldname = assignment.findDirectExpression(Expressions.FieldSub)?.concatTokens().toUpperCase();
          if (fieldname && rowFields.has(fieldname)) {
            const message = "Duplicate field assignment";
            input.issues.push(syntaxIssue(input, assignment.getFirstToken(), message));
            return VoidType.get(CheckSyntaxKey);
          }
          if (fieldname) {
            rowFields.add(fieldname);
          }
        }
      }
    }
 
    for (const s of node.findDirectExpressions(Expressions.FieldAssignment)) {
      FieldAssignment.runSyntax(s, input, targetType);
    }
 
    let type: AbstractType | undefined = undefined; // todo, this is only correct if there is a single source in the body
    for (const s of node.findDirectExpressions(Expressions.Source)) {
      type = Source.runSyntax(s, input, type);
    }
    for (const s of node.findDirectExpression(Expressions.ValueBase)?.findDirectExpressions(Expressions.Source) || []) {
      type = Source.runSyntax(s, input, type);
    }
 
    for (const foo of node.findDirectExpressions(Expressions.ValueBodyLine)) {
      if (!(targetType instanceof TableType)
          && !(targetType instanceof UnknownType)
          && !(targetType instanceof AnyType)
          && targetType !== undefined
          && !(targetType instanceof VoidType)) {
        const message = "Value, not a table type";
        input.issues.push(syntaxIssue(input, foo.getFirstToken(), message));
        return VoidType.get(CheckSyntaxKey);
      }
      let rowType: AbstractType | undefined = targetType;
      if (targetType instanceof TableType) {
        rowType = targetType.getRowType();
      }
 
      for (const l of foo.findDirectExpressions(Expressions.ValueBodyLines)) {
        for (const s of l.findDirectExpressions(Expressions.Source)) {
// LINES OF ?? todo, pass type,
          Source.runSyntax(s, input);
        }
      }
      for (const s of foo.findDirectExpressions(Expressions.FieldAssignment)) {
        FieldAssignment.runSyntax(s, input, rowType);
      }
      for (const s of foo.findDirectExpressions(Expressions.Source)) {
        const sourceType = Source.runSyntax(s, input, rowType);
        if (rowType instanceof StringType && sourceType instanceof CharacterType) {
          const message = "VALUE, source type CharacterType not compatible with StringType";
          input.issues.push(syntaxIssue(input, s.getFirstToken(), message));
        } else if (this.insertsUsingTableKey(targetType)) {
// only STANDARD tables append the rows, and thus allow padding of shorter c and x values
          const row = this.characterOrHexLength(rowType);
          const source = this.characterOrHexLength(sourceType);
          if (row !== undefined
              && source !== undefined
              && row.abap === source.abap
              && row.length !== source.length) {
            const message = `VALUE, source type ${source.abap} LENGTH ${source.length} not compatible ` +
              `with row type ${row.abap} LENGTH ${row.length}`;
            input.issues.push(syntaxIssue(input, s.getFirstToken(), message));
          }
        }
      }
    }
 
    if (letScoped === true) {
      input.scope.pop(node.getLastToken().getEnd());
    }
 
    for (let i = 0; i < forScopes; i++) {
      input.scope.pop(node.getLastToken().getEnd());
    }
 
    if (targetType?.isGeneric() && type) {
      return type;
    }
    return targetType ? targetType : type;
  }
 
  /** SORTED and HASHED tables insert the constructed rows using the table key, which
   * requires the rows to be compatible with the row type. STANDARD tables only append
   * the rows, so shorter c and x values are padded on the right instead. */
  private static insertsUsingTableKey(targetType: AbstractType | undefined): boolean {
    if (!(targetType instanceof TableType)) {
      return false;
    }
    const accessType = targetType.getAccessType();
    return accessType === TableAccessType.sorted || accessType === TableAccessType.hashed;
  }
 
  private static characterOrHexLength(type: AbstractType | undefined): {abap: string, length: number} | undefined {
    if (type instanceof CharacterType) {
      return {abap: "c", length: type.getLength()};
    } else if (type instanceof HexType) {
      return {abap: "x", length: type.getLength()};
    }
    return undefined;
  }
}