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

94.11% Statements 80/85
92.68% Branches 38/41
100% Functions 2/2
94.11% Lines 80/85

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 851x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 191x 191x 191x 191x 305x 59x 59x 59x       59x 59x 59x 59x 3x 3x 3x 3x 56x 56x 59x 59x     56x 56x 59x 59x 59x 59x 59x 59x 59x 59x 59x 1x 1x 1x 1x 305x 246x 246x 246x 246x 246x 246x 246x 1x 1x 1x 246x 246x 305x 186x 186x 186x 1x 1x 246x 246x 246x 246x 963x 6x 6x 1x 1x 5x 5x 963x 245x 245x 1x
import {ExpressionNode, TokenNode} from "../../nodes";
import {AnyType, CLikeType, CharacterType, NumericGenericType, NumericType, StringType, StructureType, UTCLongType, UnknownType, VoidType} from "../../types/basic";
import {AbstractType} from "../../types/basic/_abstract_type";
import * as Expressions from "../../2_statements/expressions";
import {Source} from "./source";
import {TypeUtils} from "../_type_utils";
import {CheckSyntaxKey, SyntaxInput, syntaxIssue} from "../_syntax_input";
import * as Tokens from "../../1_lexer/tokens";
 
export class StringTemplate {
  public static runSyntax(node: ExpressionNode, input: SyntaxInput): AbstractType {
    const typeUtils = new TypeUtils(input.scope);
    const ret = StringType.get();
 
    for (const child of node.getChildren()) {
      if (child instanceof ExpressionNode && child.get() instanceof Expressions.StringTemplateSource) {
        const s = child.findDirectExpression(Expressions.Source);
        const type = Source.runSyntax(s, input, ret);
        if (type === undefined) {
          const message = "No target type determined";
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return VoidType.get(CheckSyntaxKey);
        } else if ((typeUtils.isCharLike(type) === false
          && typeUtils.isHexLike(type) === false
          && !(type instanceof UTCLongType))
          || type instanceof StructureType) {
          const message = "String template, not character like, " + type.constructor.name;
          input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
          return VoidType.get(CheckSyntaxKey);
        }
 
        const format = child.findDirectExpression(Expressions.StringTemplateFormatting);
        const formatConcat = format?.concatTokens();
        for (const formatSource of format?.findAllExpressions(Expressions.Source) || []) {
          Source.runSyntax(formatSource, input);
        }
 
        if (format
          && formatConcat?.includes("ALPHA = ")
          && !(type instanceof UnknownType)
          && !(type instanceof VoidType)
          && !(type instanceof StringType)
          && !(type instanceof CLikeType)
          && !(type instanceof CharacterType)
          && !(type instanceof NumericGenericType)
          && !(type instanceof NumericType)
          && !(type instanceof AnyType)) {
          const message = `Cannot apply ALPHA to this type (${type.constructor.name})`;
          input.issues.push(syntaxIssue(input, format.getFirstToken(), message));
          return VoidType.get(CheckSyntaxKey);
        }
      } else if (child instanceof TokenNode) {
        const token = child.get();
        if (token instanceof Tokens.StringTemplate
          || token instanceof Tokens.StringTemplateBegin
          || token instanceof Tokens.StringTemplateMiddle
          || token instanceof Tokens.StringTemplateEnd) {
          const issue = this.validateEscapeSequences(token.getStr(), input, child);
          if (issue) {
            input.issues.push(issue);
            return VoidType.get(CheckSyntaxKey);
          }
        }
      }
    }
 
    return ret;
  }
 
  private static validateEscapeSequences(str: string, input: SyntaxInput, node: TokenNode) {
    // Valid escape sequences in ABAP string templates: \|, \{, \}, \\, \n, \r, \t
    const validEscapes = new Set(["\\|", "\\{", "\\}", "\\\\", "\\n", "\\r", "\\t"]);
 
    for (let i = 0; i < str.length; i++) {
      if (str[i] === "\\") {
        const escape = str.substring(i, i + 2);
        if (!validEscapes.has(escape)) {
          return syntaxIssue(input, node.getFirstToken(), `Invalid escape sequence "${escape}" in string template`);
        }
        i++; // skip the next character as it's part of the escape sequence
      }
    }
    return undefined;
  }
}