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 301x 301x 301x 301x 499x 101x 101x 101x       101x 101x 101x 101x 3x 3x 3x 3x 98x 98x 101x 101x     98x 98x 101x 101x 101x 101x 101x 101x 101x 101x 101x 1x 1x 1x 1x 499x 398x 398x 398x 398x 398x 398x 398x 1x 1x 1x 398x 398x 499x 296x 296x 296x 1x 1x 398x 398x 398x 398x 1624x 6x 6x 1x 1x 5x 5x 1624x 397x 397x 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;
  }
}